---
description: July 19, 2024
---

# 1.0.0-Beta6

BoxLang Betas are released weekly.  This is our fifth beta marker.  Here are the release notes.

## New Features

### [BL-157](https://ortussolutions.atlassian.net/browse/BL-157) Implement nested transactions

We are excited to bring the completion of nested transactions into BoxLang, something we have wanted in an open-source engine for years.

```cfscript
transaction{
  queryExecute( "INSERT INTO developers ( id, name, role ) VALUES ( 22, 'Brad Wood', 'Developer' )", {} );
    
    transaction{
      queryExecute( "INSERT INTO developers ( id, name, role ) VALUES ( 33, 'Jon Clausen', 'Developer' )", {} );
	 transactionRollback();
    }
    
}
```

Here, the `INSERT` in the child transaction is rolled back, but the parent transaction's `INSERT` statement persists.

General behavior:

* A rollback on the parent transaction will roll back the child transaction.
* A rollback on the child will **NOT** roll back the parent.
* Child transaction savepoints use a prefix so they don't collide with the parent transaction.
* Logging has been added, so you can easily see the transaction lifecycle as a transaction is created, savepoints are created, transactions rollback, etc.
* You can't change any transaction properties on a nested transaction. i.e., once the parent transaction begins, you can't begin a child transaction and change the isolation level.

Transaction events will come in the next beta.



### [BL-348](https://ortussolutions.atlassian.net/browse/BL-348) queryReverse() finalized

This BIF is now complete so you can easily reverse query data.



### [BL-349](https://ortussolutions.atlassian.net/browse/BL-349) Allow servlet to expand paths so CommandBox web aliases or ModCFML web dirs can be used and new `OnMissingMapping` event.

Adobe CFML Engines do this today via their “magic” connector. Lucee has never supported it.  The expanded path should work like so now:

* if it doesn’t start with a `/` (and isn’t absolute), then make it relative to the base path
* look for a BoxLang mapping (not including “/” )
* Check if it’s an absolute path already, like C:/foo/bar
* Then announce an `ON_MISSING_MAPPING` interception point
* if no interceptor provided a value, then resolve via the root `/` mapping

The servlet runtime will have a listener for `ON_MISSING_MAPPING` that attempts to use `servetContext.getRealPath()` to try and resolve the path. This will allow any aliases in the resource manager to kick in.

If the servlet has an alias called `/foo` and you expand the path `/foo/existingFile.txt`, it will see it, but if you expand the path `/foo/notExistingFile.txt` then it will not “see” the mapping. We can solve that by taking off segments to see if any part of the path is found, but there is a performance hit to that, and I’m not sure if it’s necessary or not.



### [BL-350](https://ortussolutions.atlassian.net/browse/BL-350) Allow static functional access to BIFs

We have introduced static access to headless BIFS in BoxLang so you can use them as method references anywhere a BIF can be received, EVEN in Java classes that implement similar BIFS.

```java
// Syntax
::BIF

// Examples
::UCase
::hash
```

This expression represents a first-class BoxLang function that can be invoked directly.

```cfscript
(::reverse)( "darb" ); // brad
```

You can also store the BIF static access into variables.

```cfscript
foo = ::ucase
foo( "test" ) // TEST
```

You can also leverage it as a high-order function combination from methods or classes that expect function references.

```cfscript
["brad","luis","jon"].map( ::ucase ); // [ "BRAD","LUIS","JON" ]

[1.2, 2.3, 3.4].map( ::ceiling );    // [2,3,4]

["brad","luis","jon"].map( ::hash ); // ["884354eb56db3323cbce63a5e177ecac", "502ff82f7f1f8218dd41201fe4353687", "006cb570acdab0e0bfc8e3dcb7bb4edf" ]
```



### [BL-351](https://ortussolutions.atlassian.net/browse/BL-351) Allow Functional binding to member functions

In order to explain this new functionality in BoxLang, here is a Java snippet:

```java
List.of("apple", "banana", "cherry").stream().forEach(String::toUpperCase);
```

&#x20;This syntax is a shortcut for writing out the following Java lambda:

```java
List.of("apple", "banana", "cherry").stream().forEach(str -> str.toUpperCase());
```

(Other JVM languages like Clojure have similar syntax) The `String::toUpperCase` binds to the instance method `toUpperCase`, but in a way that it will be called on each string instance in the stream.  So for each iteration, it will call the `toUpperCase` method on that instance.  Nothing like this ever existed in CFML engines.  However, in BoxLang, it does now.   BoxLang allows you to do the following:

* BoxLang-type member methods
* Class member methods
* Java member methods
* Any object member/field

Since member methods in BoxLang aren’t necessarily bound to a class and we’re a dynamic language, we have simplified the syntax to this:

```
.methodName
```

This is a function expression that accepts a **single** argument and calls the method name specified on the incoming instance, returning the result of the member call. it is a shortcut for

```
i -> i.methodName()
```

So our Java example becomes like this in BoxLang

```java
["apple", "banana", "cherry"].stream().forEach( .toUpperCase );
```

When not using (), check if there is a field of that name that is not a function, and if so, return that field value. So, the following example would fetch the name key in the struct.

```cfscript
nameGetter = .name
data = { name : "brad", hair : "red" }
nameGetter( data ) // brad
```

If the syntax `.foo()` is used or there is no field of that name, then it will be assumed we’re invoking a method.  It can be directly invoked:

```cfscript
(.reverse)( "darb" ); // brad
```

It can be placed into a variable and invoked later:

```cfscript
foo = .ucase;
foo( "test" );  // TEST
```

And it can be passed to higher-order functions. (This is the main use case)

```cfscript
["brad","luis","jon"].map( .toUpperCase ); // ["BRAD","LUIS","JON"]

[1.2, 2.3, 3.4].map( .ceiling ); // [2,3,4]

[
  { myFunc : ()->"eric" },
  { myFunc : ()->"gavin" }
].map( .myFunc ) // [ "eric", "gavin" ]
```

Additionally, we have added support for multiple-arguments as well:

```cfscript
.methodName( arg1, arg2 )
```

Example:

```cfscript
["brad","luis","jon"].map( .left(1) ); // [ "b", "l", "j" ]
```

The arguments will not be evaluated until the function is invoked, and the argument expressions will be re-evaluated for every invocation.&#x20;

```cfscript
pendingOrders.each( .submit( generateNextOrderNumber() ) ) // Fresh order number for each submission
```

The argument evaluation will have lexical binding to the declaring context.

```cfscript
local.suffix = " Sr."
["brad","luis","jon"].map( .concat( suffix ) ); // [ "brad Sr.", "luis Sr.", "jon Sr." "]
```

or

```cfscript
children.each( .setParent( this ) )
```

Bound member methods can also use named params (unless they are Java methods, of course)

```
foo = .left( count=2 );
foo( "test" ); // "te"
```

This brings a whole new dimension of dynamic goodness for not only BoxLang functions but also for Java functions.  Welcome to a new era!



### [BL-352](https://ortussolutions.atlassian.net/browse/BL-352) arrayRange() BIF to create arrays with a specific range of values

This is one of our very first steps in supporting range types.  You know have the capability to generate arrays with a specific boxed range using `arrayRange( from, to )`.  You can use a `from` and `to` numeric index, and it will build the array with that many elements with the range as its value.  However, you can also use range notation: `{from}..{to}`

```cfscript
// an array from 1 to 100
a = arrayRange( "1..100" )
a = arrayRange( 1, 100 )

// a negative indexed array
a = arrayRange( "-10..10" )
a = arrayRange( -10, 10 )
```



### [BL-353](https://ortussolutions.atlassian.net/browse/BL-353) threadTerminate() finalized

We have now finalized a `threadTerminate( name )` BIF.



### [BL-354](https://ortussolutions.atlassian.net/browse/BL-354) threadJoin() BIF Finalized

We have now finalized a `threadJoin( [name], timeout )` BIF.  We have expanded this BIF and if you don't pass a thread name or a list of names, it will join all active threads.&#x20;



### [BL-355](https://ortussolutions.atlassian.net/browse/BL-355) queryInsertAt() BIF finalized

This is now finalized.

### [BL-356](https://ortussolutions.atlassian.net/browse/BL-356) QueryRowSwap() bif finalized

This is now finalized.



### [BL-358](https://ortussolutions.atlassian.net/browse/BL-358) runAsync() completed but based on the powerful new BoxFuture -> CompletableFuture

This is a big ticket.  Welcome to the future with `BoxFuture.` We have completed the ability to do asynchronous pipelines and parallel computations in BoxLang.  We have introduced a new type called `BoxFuture,` which extends a Java Completable Future.  The return of a `runAsync()` is a BoxFuture, and you can create pipelines to process the computations.  You can access all the methods in a Completable future and many more.  This will be documented in our [Async Programming](../../../boxlang-framework/asynchronous-programming) guide.

#### Futures

You have a new BIF: `futureNew( [value], [executor] )` that can create new box futures.   By default it will create incomplete and empty futures.  However, you can pass different values to the future.  Check out the API Docs: [https://s3.amazonaws.com/apidocs.ortussolutions.com/boxlang/1.0.0-beta6/ortus/boxlang/runtime/async/BoxFuture.html](https://s3.amazonaws.com/apidocs.ortussolutions.com/boxlang/1.0.0-beta6/ortus/boxlang/runtime/async/BoxFuture.html)

* `value` : If passed, the value to set on the BoxFuture object as completed, or it can be a lambda/closure that will provide the value, and it will be executed asynchronously, or it can be a native Java CompletableFuture
* `executor` : If passed, it will use this executor to execute the future.  Otherwise, it defaults to the fork/join pool in Java.

```cfscript
// incomplete future
f = futureNew()

// completed future with a value
f = futureNew( myValue )

// a future that executes an asynchronous lambda/closure and the value will be the result
f = futureNew( () -> orderService.calculate() )

// A future with an executable and a virtual thread executor
f = futureNew(
    () -> processTasks(),
    executorNew( "virtual", "virtual" )
)
```

{% embed url="https://s3.amazonaws.com/apidocs.ortussolutions.com/boxlang/1.0.0-beta6/ortus/boxlang/runtime/async/BoxFuture.html" %}

#### runAsync( callback, \[executor ] )

You can pass a closure or lambda to `runAsync()` to execute it asynchronously.  The seconds parameter is an `executor` which runs the threads.  It runs in the fork/join pool by default, but you can also pass your own executors.  The return is a BoxFuture, which you can then do asynchronous pipelines.

```cfscript
calculation = runAsync( () -> calculateNumber() )
    .then( result -> result * 2 )
    .then( result -> result + 5 )
    .onError( exception -> { manage exception } )
    .get()
    

// Use a custom executor
runAsync( () -> "Hello", executorNew( "single", "single" ) )
    .then( ::println )
    .join()
    
// Process an incoming order
runAsync( () => orderService.getOrder() )
    .then( order => enrichOrder( order ) )
    .then( order => performPayment( order ) )
    .thenAsync( 
        order => dispatchOrder( order ), 
        executorNew( "cpuIntensive", "fixed", 150 )
     )
    .then( order => sendConfirmation( order ) );
 
// Combine Futures
var bmi = runAsync( () => weightService.getWeight( rc.person ) )
    .thenCombine(
	runAsync( 
	    () => heightService.getHeight( rc.person ) ),
            ( weight, height ) => {
            var heightInMeters = arguments.height/100;
            return arguments.weight / (heightInMeters * heightInMeters );
            })
        .get();   
```





### [BL-359](https://ortussolutions.atlassian.net/browse/BL-359) BIF Collection for managing and interacting with async service executors: list, get, has, new, shutdown

You now have the full capability to register, create, and manage custom executors in BoxLang.    The `AsyncService` manages all executors in BoxLang.

Here are the new functions dealing with executors:

* `executorGet( name )`: Get a registered executor
* `executorHas( name ):` Checks if an executor has been registered or not
* `executorList()` : Lists all registered executors
* `executorNew( name, type, [maxThreads:20] ):` Creates and registers an executor by type and max threads if applicable.
* `executorShutdown( name )`: Shuts down an executor
* `executorStatus( [name] )`: Get a struct of executor metadata and stats by name or all.

The available executor types are:

* `cached` - Creates a cached thread pool executor.
* `fixed` - Creates a fixed thread pool executor.
* `fork_join` - Creates a fork-join pool executor.
* `scheduled` - Creates a scheduled thread pool executor.
* `single` - Creates a single thread executor.
* `virtual` - Creates a virtual thread executor.
* `work_stealing` - Creates a work-stealing thread pool executor.



### [BL-360](https://ortussolutions.atlassian.net/browse/BL-360) Configuration now supports executor registration for global usage

You can now register your executors globally in BoxLang in the `boxlang.json`

```json
// Global Executors for the runtime
// These are managed by the AsyncService and registered upon startup
// The name of the executor is the key and the value is a struct of executor settings
// Types are: cached, fixed, fork_join, scheduled, single, virtual, work_stealing
// The `threads` property is the number of threads to use in the executor. The default is 20
// Some executors do not take in a `threads` property
"executors": {
	"boxlang-tasks": {
		"type": "scheduled",
		"threads": 20
	},
	"cacheservice-tasks": {
		"type": "scheduled",
		"threads": 20
	}
},
```



### [BL-124](https://ortussolutions.atlassian.net/browse/BL-124) Implement primary/foreign key columns in dbInfo

DBInfo now returns primary and foreign key columns when you need them.



### [BL-255](https://ortussolutions.atlassian.net/browse/BL-255) Implement QueryMeta class for $.bx.meta or $.bx.getMeta() debugging support

Metaprogramming on queries is now available with much more information like caching, execution status, records, etc.

## Improvements

### [BL-357](https://ortussolutions.atlassian.net/browse/BL-357) "Fix" return types of BIFs that return "true" for no real reason

One of the odd things about CFML is the following:

```cfscript
myArr.append( "foo" ) // returns myArr
arrayAppend( myArr ) // returns true??
```

In BoxLang, the following BIFS returns the instance it was called with instead of a boolean.  However, if you are in compat mode in a CFML file, you will get the old behavior.

* `arrayAppend()`
* `arrayClear()`
* `arrayDeleteAt()`
* `arrayInsertAt()`
* `arrayResize()`
* `arraySet()`
* `arraySwap()`
* `StructClear()`
* `StructKeyTranslate()`
* `StructInsert()`
* `structAppend()`
* `QuerySetRow()`
* `QueryDeleteRow()`
* `QuerySort()`
* `ArrayPrepend()`

The following BIFs return something other than the original data structure, but they have a good reason for doing so, so they remain as is:

* queryAddColumn() -- returns index of the column removed
* queryAddRow() -- returns the row number added

The following BIF returns the query in Adobe, which we’ll match. Lucee returns the array of removed data, but we’re ignoring that since we agree with Adobe’s behavior.

* QueryDeleteColumn()



### [BL-366](https://ortussolutions.atlassian.net/browse/BL-366) Provide Java FI typing on all higher-order BIFs

### [BL-371](https://ortussolutions.atlassian.net/browse/BL-371) make name un-required in the application component

### [BL-346](https://ortussolutions.atlassian.net/browse/BL-346) DynamicObject equals() hashCode() to allow for dynamic checking

## Bugs

[BL-364](https://ortussolutions.atlassian.net/browse/BL-364) Overridden getter in parent class not being used

[BL-370](https://ortussolutions.atlassian.net/browse/BL-370) List and Delmiter Args are not correct in `ListReduce` callback

[BL-365](https://ortussolutions.atlassian.net/browse/BL-365) The throwable Dump table is not styled

