Skip to content
Merged

v14 #326

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
f8b7f35
ci: disable Lucee bleeding-edge checks (#316)
elpete Aug 12, 2026
d0c24be
fix(SchemaBuilder): allow timestamps in alter statements (#314)
elpete Aug 12, 2026
c982172
feat(QueryBuilder): add named return formatters (#315)
elpete Aug 12, 2026
e7db379
feat(QueryBuilder): add cross-grammar JSON support (#317)
elpete Aug 12, 2026
5ad6b22
feat(QueryBuilder): validate duplicate select columns (#318)
elpete Aug 12, 2026
8141a24
feat(QueryBuilder): add whereInBulk (#319)
elpete Aug 12, 2026
63dda74
fix(SqlServerGrammar): replace defaults when modifying columns (#320)
elpete Aug 12, 2026
2adc4ce
feat(QueryBuilder): add parameter-aware bulk inserts (#321)
elpete Aug 12, 2026
8fe6860
fix(SqlServerGrammar): compile ordered union branches (#322)
elpete Aug 12, 2026
29808b3
fix(SchemaBuilder): qualify tables with default schema (#323)
elpete Aug 12, 2026
982ab96
feat(QueryBuilder): match null upsert targets (#324)
elpete Aug 12, 2026
a0993ac
feat(moduleconfig): add shouldWrapValues setting (#325)
elpete Aug 12, 2026
5ebfa68
test(QueryBuilder): cover unlimited grammar parameter limits
elpete Aug 13, 2026
337bdb7
fix(QueryBuilder): address v14 regression audit
elpete Aug 13, 2026
4c65f7e
fix: resolve query and schema regression audit
elpete Aug 13, 2026
e027062
fix: isolate query state and full-null behavior
elpete Aug 13, 2026
ebd4007
fix: restore query and schema builder lifecycle
elpete Aug 13, 2026
f3a1c28
fix(QueryBuilder): preserve compiled binding alignment
elpete Aug 13, 2026
f32c805
fix(QueryBuilder): preserve specialized query behavior
elpete Aug 13, 2026
4767f6e
fix(QueryBuilder): preserve query utility semantics
elpete Aug 13, 2026
b321568
fix(QueryBuilder): preserve binding ownership
elpete Aug 14, 2026
5fa72a5
fix(Grammar): correct dialect edge cases
elpete Aug 14, 2026
d337a40
fix: harden query and schema compilation
elpete Aug 14, 2026
93810e5
fix: harden query and schema edge cases
elpete Aug 15, 2026
4554f99
fix: preserve nested SQL compilation semantics
elpete Aug 15, 2026
40be7b0
fix: isolate nested query state and DML CTEs
elpete Aug 15, 2026
168ef43
fix: close remaining state ownership gaps
elpete Aug 15, 2026
80b60bf
fix: align nested query and binding compilation
elpete Aug 15, 2026
564b2e5
fix: scope alter view bindings to creation
elpete Aug 15, 2026
6b7ca55
fix: harden dialect parsing edge cases
elpete Aug 15, 2026
ae0072a
fix: preserve Adobe ColdFusion compatibility
elpete Aug 16, 2026
4cada11
fix: handle omitted null JSON bindings
elpete Aug 16, 2026
a57319c
ci: exclude unsupported Adobe full-null checks
elpete Aug 16, 2026
48b5f43
ci: increase stable BoxLang test heap
elpete Aug 16, 2026
16f830d
ci: use compact TestBox reports
elpete Aug 16, 2026
4a8b0b0
fix(QueryBuilder): keep generated methods within JVM limits
elpete Aug 16, 2026
777834c
fix(QueryBuilder): preserve explicit BoxLang JSON nulls
elpete Aug 16, 2026
d6c9e92
fix(QueryBuilder): normalize full-null JSON shortcuts
elpete Aug 16, 2026
fd1c76f
refactor(QueryBuilder): extract lazy collaborators
elpete Aug 16, 2026
c2a8230
fix(QueryBuilder): handle aliases and null edge cases
elpete Aug 16, 2026
c17bbc2
fix(JsonQueryClause): preserve BoxLang null containment shortcuts
elpete Aug 16, 2026
3d1f372
fix(SqlServerGrammar): declare aliases in data modifications
elpete Aug 16, 2026
29cfd5f
fix(QueryBuilder): preserve nested query invariants
elpete Aug 16, 2026
17912d3
test(QueryBuilder): split abstract specs by behavior
elpete Aug 16, 2026
90f7be4
refactor(QueryBuilder): extract join clause manager
elpete Aug 16, 2026
6ff8d71
refactor(QueryBuilder): extract predicate clause
elpete Aug 16, 2026
5bd7eee
fix: resolve query and schema regressions
elpete Aug 17, 2026
37ba48e
fix: support Adobe null predicate arguments
elpete Aug 17, 2026
594b3c3
fix: support concurrent Adobe compilation
elpete Aug 17, 2026
6ec7ca3
fix: harden query edge case handling
elpete Aug 17, 2026
1afcaf5
fix: keep failed clause mutations atomic
elpete Aug 17, 2026
73f15c2
fix: preserve query state after binding errors
elpete Aug 17, 2026
f2f6b4b
fix(QueryUtils): preserve BigDecimal scale on BoxLang
elpete Aug 17, 2026
14568d5
fix(QueryUtils): handle nested PostgreSQL comments
elpete Aug 17, 2026
2271b9b
fix(QueryBuilder): replace bindings between updates
elpete Aug 17, 2026
4dd02c7
fix(QueryBuilder): replace bindings between inserts
elpete Aug 17, 2026
1fef5da
fix(QueryBuilder): replace bindings between insert ignores
elpete Aug 17, 2026
736cd5d
fix(QueryBuilder): preserve orders after failed reorder
elpete Aug 17, 2026
4bd5de6
fix(QueryBuilder): preserve returning state on errors
elpete Aug 17, 2026
1e02fa9
fix(QueryBuilder): restore bindings after failed upserts
elpete Aug 17, 2026
f3c849f
fix(QueryBuilder): restore bindings after failed insert using
elpete Aug 17, 2026
bc2cfba
fix(QueryBuilder): restore bindings after failed bulk inserts
elpete Aug 17, 2026
474ee92
fix(QueryUtils): infer byte values as integers
elpete Aug 17, 2026
6fd446b
fix(QueryUtils): infer Java dates as timestamps
elpete Aug 17, 2026
b50fa3f
fix(SQLCommenter): handle Oracle quoted literals
elpete Aug 17, 2026
5a9caf7
fix(JsonQueryClause): parse negative array indexes
elpete Aug 17, 2026
47dc856
fix(QueryBuilder): keep raw order bindings atomic
elpete Aug 17, 2026
b015500
fix(QueryUtils): normalize prefixed SQL types
elpete Aug 17, 2026
9a6f92b
fix(PostgresGrammar): preserve text default colons
elpete Aug 17, 2026
e1a39f3
fix(QueryUtils): render Boolean SQL literals
elpete Aug 17, 2026
2f60d63
fix(QueryBuilder): clamp pagination page offsets
elpete Aug 17, 2026
61850fa
fix(QueryBuilder): clamp negative row bounds
elpete Aug 17, 2026
66a7db0
fix(PredicateClause): preserve null IN values
elpete Aug 17, 2026
1bd3e15
fix(QueryBuilder): preserve null expression bindings
elpete Aug 17, 2026
448de7e
fix(QueryUtils): render CLOB values as text
elpete Aug 17, 2026
f411b7a
fix(QueryBuilder): restore value query columns
elpete Aug 17, 2026
629e950
fix(QueryUtils): normalize inferred SQL types
elpete Aug 17, 2026
5f1f22e
fix(QueryBuilder): normalize pagination metadata pages
elpete Aug 17, 2026
ab7a66f
fix(QueryBuilder): preserve pretend mode on reset
elpete Aug 17, 2026
e0f07ba
fix(QueryBuilder): omit wildcard insert targets
elpete Aug 17, 2026
5e4cf79
fix(OracleGrammar): preserve user row number columns
elpete Aug 17, 2026
5272eee
fix(DerbyGrammar): preserve user row number columns
elpete Aug 17, 2026
3beac8f
fix(QueryBuilder): prevent duplicate cross joins
elpete Aug 17, 2026
1089717
fix(SchemaBuilder): apply configured default options
elpete Aug 17, 2026
f64d526
fix(PostgresGrammar): compile multi-join updates
elpete Aug 17, 2026
8a5382f
fix(SQLiteGrammar): compile multi-join updates
elpete Aug 17, 2026
44519e3
fix(SQLiteGrammar): order update row limits
elpete Aug 17, 2026
666483b
fix(MySQLGrammar): preserve delete row selection
elpete Aug 17, 2026
065fd53
fix(SQLiteGrammar): preserve delete row selection
elpete Aug 17, 2026
ebabaf7
fix(MySQLGrammar): preserve update row selection
elpete Aug 17, 2026
9153f20
fix(SQLiteGrammar): preserve update ordering
elpete Aug 17, 2026
b430694
fix(MySQLGrammar): reject DML offsets
elpete Aug 17, 2026
7b8a0f7
fix(MySQLGrammar): reject joined delete row selection
elpete Aug 17, 2026
2ec69be
fix(SqlServerGrammar): preserve delete limits
elpete Aug 17, 2026
3c2699e
fix(SqlServerGrammar): reject unsupported DML selection
elpete Aug 17, 2026
175a0fd
fix(PostgresGrammar): reject unsupported DML selection
elpete Aug 17, 2026
642d87d
fix(OracleGrammar): reject unsupported DML selection
elpete Aug 17, 2026
7aac569
fix(DerbyGrammar): reject unsupported DML selection
elpete Aug 17, 2026
1baad0c
fix(QueryUtils): normalize untyped null bindings
elpete Aug 17, 2026
a2023e9
fix(SQLiteGrammar): reject added auto-increment columns
elpete Aug 17, 2026
361e630
fix(SQLiteGrammar): reject added unique columns
elpete Aug 17, 2026
38d7f63
fix(SQLiteGrammar): reject added stored columns
elpete Aug 17, 2026
e9ae5ef
ci: enable Adobe full-null test matrix
elpete Aug 17, 2026
01b9fc4
test: support Adobe row number regressions
elpete Aug 17, 2026
11680aa
fix(SQLiteGrammar): support Adobe array compilation
elpete Aug 17, 2026
d1ae7fd
Apply cfformat changes
elpete Aug 17, 2026
303eb05
ci: check formatting without auto-commit
elpete Aug 18, 2026
fdacedb
ci: patch TestBox for Adobe full null
elpete Aug 18, 2026
78e9641
ci: complete TestBox full null patch
elpete Aug 18, 2026
c6855d1
fix: support Adobe full null execution
elpete Aug 18, 2026
aee9332
fix: guard missing TestBox skip metadata
elpete Aug 18, 2026
2668a50
ci: run experimental engines with full null support
elpete Aug 18, 2026
b972af1
fix(QueryBuilder): support external subclass collaborators
elpete Aug 22, 2026
09fc92b
fix(QueryUtils): infer BIGINT for large integers
elpete Aug 22, 2026
e4d26d1
refactor: reduce internal closure usage
elpete Aug 25, 2026
a53a9aa
perf: remove production copy calls
elpete Aug 25, 2026
d2d0347
perf: optimize query construction hot paths
elpete Aug 26, 2026
9369af3
perf: reduce query construction allocations
elpete Aug 26, 2026
007269f
Add returning row grammar capabilities
elpete Aug 26, 2026
e690ecb
fix: safely widen numeric list bindings
elpete Sep 9, 2026
0908059
fix(ci): drop obsolete TestBox full-null patch
elpete Sep 15, 2026
cc350f6
fix(ci): use TestBox CLI JSON reporter
elpete Sep 15, 2026
bb5ddec
fix: unwrap retrievable query builders before snapshotting
elpete Aug 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/cron.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ jobs:
experimental: [ false ]
fullNull: ["true", "false"]
include:
- cfengine: "lucee@be"
experimental: true
- cfengine: "adobe@be"
experimental: true
- cfengine: "boxlang@be"
Expand All @@ -42,6 +40,8 @@ jobs:
box install

- name: Start server
env:
FULL_NULL: ${{matrix.fullNull}}
run: |
box server start serverConfigFile="server-${{ matrix.cfengine }}.json" --noSaveSettings --debug
curl http://127.0.0.1:60298
Expand All @@ -50,4 +50,4 @@ jobs:
env:
FULL_NULL: ${{matrix.fullNull}}
continue-on-error: ${{ matrix.experimental }}
run: box testbox run
run: box testbox run
13 changes: 5 additions & 8 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ jobs:
experimental: [ false ]
fullNull: ["true", "false"]
include:
- cfengine: "lucee@be"
experimental: true
- cfengine: "adobe@be"
experimental: true
fullNull: "true"
- cfengine: "boxlang@be"
experimental: true
fullNull: "true"
steps:
- name: Checkout Repository
uses: actions/checkout@v7
Expand All @@ -50,6 +50,8 @@ jobs:
box install

- name: Start server
env:
FULL_NULL: ${{matrix.fullNull}}
run: |
box server start serverConfigFile="server-${{ matrix.cfengine }}.json" --noSaveSettings --debug
curl http://127.0.0.1:60298
Expand Down Expand Up @@ -80,9 +82,4 @@ jobs:
run: box install commandbox-cfformat

- name: Run CFFormat
run: box run-script format

- name: Commit Format Changes
uses: stefanzweifel/git-auto-commit-action@v7.2.0
with:
commit_message: Apply cfformat changes
run: box run-script format:check
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ jobs:
box install

- name: Start server
env:
FULL_NULL: ${{matrix.fullNull}}
run: |
box server start serverConfigFile="server-${{ matrix.cfengine }}.json" --noSaveSettings --debug
curl http://127.0.0.1:60298
Expand Down
31 changes: 27 additions & 4 deletions ModuleConfig.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ component {
"defaultReturnFormat": "array",
"preventDuplicateJoins": false,
"validateOperatorsAndCombinators": true,
"validateDuplicateSelectColumns": false,
"validateQueryExecuteReturnType": false,
"collectQueryLog": true,
"convertEmptyStringsToNull": true,
"shouldWrapValues": true,
"validateQueryParamStructKeys": true,
"numericSQLType": "NUMERIC",
"throwOnUnsafeNumericInference": false,
"integerSQLType": "INTEGER",
"bigIntegerSQLType": "BIGINT",
"decimalSQLType": "DECIMAL",
"defaultOptions": {},
"sqlCommenter": {
Expand All @@ -29,7 +33,8 @@ component {
},
"shouldMaxRowsOverrideToAll": function( maxRows ) {
return maxRows <= 0;
}
},
"returnFormatters": {}
};

interceptorSettings = { "customInterceptionPoints": "preQBExecute,postQBExecute" };
Expand All @@ -51,17 +56,27 @@ component {
.to( "qb.models.Query.QueryUtils" )
.initArg( name = "convertEmptyStringsToNull", value = settings.convertEmptyStringsToNull )
.initArg( name = "validateQueryParamStructKeys", value = settings.validateQueryParamStructKeys )
.initArg( name = "numericSQLType", value = settings.numericSQLType )
.initArg( name = "throwOnUnsafeNumericInference", value = settings.throwOnUnsafeNumericInference )
.initArg( name = "integerSQLType", value = settings.integerSQLType )
.initArg( name = "bigIntegerSQLType", value = settings.bigIntegerSQLType )
.initArg( name = "decimalSQLType", value = settings.decimalSQLType );

binder
.map( alias = "ReturnFormatterRegistry@qb", force = true )
.to( "qb.models.Query.ReturnFormatterRegistry" )
.initArg( name = "utils", ref = "QueryUtils@qb" )
.initArg( name = "returnFormatters", value = settings.returnFormatters );

binder
.map( alias = "QueryBuilder@qb", force = true )
.to( "qb.models.Query.QueryBuilder" )
.initArg( name = "grammar", ref = settings.defaultGrammar )
.initArg( name = "utils", ref = "QueryUtils@qb" )
.initArg( name = "returnFormatterRegistry", ref = "ReturnFormatterRegistry@qb" )
.initArg( name = "preventDuplicateJoins", value = settings.preventDuplicateJoins )
.initArg( name = "validateOperatorsAndCombinators", value = settings.validateOperatorsAndCombinators )
.initArg( name = "validateDuplicateSelectColumns", value = settings.validateDuplicateSelectColumns )
.initArg( name = "validateQueryExecuteReturnType", value = settings.validateQueryExecuteReturnType )
.initArg( name = "collectQueryLog", value = settings.collectQueryLog )
.initArg( name = "returnFormat", value = settings.defaultReturnFormat )
.initArg( name = "defaultOptions", value = settings.defaultOptions )
Expand All @@ -71,7 +86,15 @@ component {
binder
.map( alias = "SchemaBuilder@qb", force = true )
.to( "qb.models.Schema.SchemaBuilder" )
.initArg( name = "grammar", ref = settings.defaultGrammar );
.initArg( name = "grammar", ref = settings.defaultGrammar )
.initArg( name = "defaultOptions", value = settings.defaultOptions );

// Apply shouldWrapValues setting to the configured grammar singleton.
// When defaultGrammar is AutoDiscover@qb, the setting is forwarded via
// onMissingMethod to whatever concrete grammar AutoDiscover resolves at runtime.
if ( structKeyExists( settings, "shouldWrapValues" ) ) {
wirebox.getInstance( settings.defaultGrammar ).setShouldWrapValues( settings.shouldWrapValues );
}
}

}
100 changes: 99 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,23 @@ Using qb, you can:

Installation is easy through [CommandBox](https://www.ortussolutions.com/products/commandbox) and [ForgeBox](https://www.coldbox.org/forgebox). Simply type `box install qb` to get started.

## Numeric list inference

qb combines numeric array members using a common SQL type that covers their declared ranges. For example, `[ 1, 3000000000 ]` uses `BIGINT`, and integers mixed with fractional values use `DECIMAL`. Explicit member types such as `TINYINT`, `SMALLINT`, `REAL`, `FLOAT`, and `DOUBLE` also participate in inference.

When there is no portable numeric promotion without potential precision loss, qb falls back to `VARCHAR`. Examples include `BIGINT` mixed with `DOUBLE`, and `DECIMAL` mixed with `FLOAT`. This preserves the binding representation; the database can still apply its own conversion when executing the query.

We recommend enabling `throwOnUnsafeNumericInference` in development to catch these combinations early:

```cfc
// config/ColdBox.cfc, in your development environment configuration
moduleSettings.qb.throwOnUnsafeNumericInference = true;
```

The setting defaults to `false`. When enabled, unsafe numeric array inference throws `QBUnsafeNumericInference` with the conflicting SQL types. Safe promotions and ordinary mixed text arrays retain their normal behavior. For standalone usage, pass `throwOnUnsafeNumericInference = true` to the `QueryUtils` constructor.

An explicit `cfsqltype` or `sqltype` on the outer binding always takes precedence; this setting does not validate caller-selected conversions or database column precision and scale.

## Code Samples

Compare these two examples:
Expand Down Expand Up @@ -71,6 +88,88 @@ q = queryExecute(

qb enables you to explore new ways of organizing your code by letting you pass around a query builder object that will compile down to the right SQL without you having to keep track of the order, whitespace, or other SQL gotchas!

## Bulk WHERE IN

For large value collections, `whereInBulk` serializes the values into one bound parameter and lets the active grammar expand them into rows. This avoids database parameter limits without changing the behavior or performance of regular `whereIn` calls.

```cfc
query
.from( "users" )
.whereInBulk( "id", userIds )
.get();
```

qb infers a common type from the values and translates it to the active database grammar. Matching `cfsqltype` values in query parameter structs are preserved. Mixed values fall back to the grammar's string type.

You can pass an explicit `sqlType` as the third argument when the column needs a more specific database type, such as `BIGINT`, `UUID`, or a particular decimal precision:

```cfc
query
.from( "users" )
.whereInBulk( "id", userIds, "BIGINT" )
.get();
```

The explicit `sqlType` should match the constrained column so the database can avoid implicit conversions. `whereNotInBulk`, `andWhereInBulk`, `orWhereInBulk`, `andWhereNotInBulk`, and `orWhereNotInBulk` are also available.

Bulk value expansion is supported by these grammars and database features:

+ SQL Server 2016+ using `OPENJSON`; database compatibility level 130+ is required
+ PostgreSQL 9.4+ using `JSONB_ARRAY_ELEMENTS_TEXT`
+ MySQL 8.0.4+ and MariaDB 10.6+ using `JSON_TABLE`
+ Oracle Database 12c Release 1 (12.1.0.2)+ using `JSON_TABLE`
+ SQLite with JSON functions enabled; they are built in by default as of SQLite 3.38.0

Derby does not support bulk value expansion and throws an `UnsupportedOperation` exception for non-empty collections.

## Development Validation

qb can detect statically identifiable duplicate select output names before they are silently collapsed by CFML query results. Enable this validation in development and leave it disabled in production:

```cfc
moduleSettings = {
"qb": {
"validateDuplicateSelectColumns": true
}
};
```

The validation checks the final selection when the query is compiled, including simple columns, explicit aliases, subselect aliases, and explicitly aliased typed columns. Wildcards and expressions without explicit aliases are skipped because their output names cannot be known until the query executes.

## Return Formatters

qb includes named return formatters for `array`, `query`, `none`, and `struct`. The `struct` formatter returns a struct of rows keyed by a selected column:

```cfc
usersByUsername = query
.setReturnFormat( "struct", { "columnKey": "username" } )
.from( "users" )
.get();
```

Applications can register reusable custom formatter factories in their qb module settings:

```cfc
moduleSettings = {
"qb": {
"returnFormatters": {
"ids": function( options ) {
return function( q ) {
return queryColumnData( q, options.column );
};
}
}
}
};

ids = query
.setReturnFormat( "ids", { "column": "id" } )
.from( "users" )
.get();
```

Formatter factories can also be WireBox mapping names or components with a `toFormatter( options )` method.

Here's a gist with an example of the powerful models you can create with this!
https://gist.github.com/elpete/80d641b98025f16059f6476561d88202

Expand Down Expand Up @@ -118,4 +217,3 @@ For both Lucee and ACF you need to set the JDBC Driver class to `org.sqlite.JDBC
## Full Docs

You can browse the full documentation at https://qb.ortusbooks.com

45 changes: 35 additions & 10 deletions models/Grammars/AutoDiscover.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,59 @@ component singleton {

property name="wirebox" inject="wirebox";
property name="grammar";
property name="shouldWrapValues";

function autoDiscoverGrammar() {
cfdbinfo( type = "Version", name = "local.dbInfo" );

var discoveredGrammar = "";
switch ( dbInfo.DATABASE_PRODUCTNAME ) {
case "MySQL":
case "MariaDB":
return wirebox.getInstance( "MySQLGrammar@qb" );
discoveredGrammar = wirebox.getInstance( "MySQLGrammar@qb" );
break;
case "Derby":
return wirebox.getInstance( "DerbyGrammar@qb" );
discoveredGrammar = wirebox.getInstance( "DerbyGrammar@qb" );
break;
case "PostgreSQL":
return wirebox.getInstance( "PostgresGrammar@qb" );
discoveredGrammar = wirebox.getInstance( "PostgresGrammar@qb" );
break;
case "Microsoft SQL Server":
return wirebox.getInstance( "SQLServerGrammar@qb" );
discoveredGrammar = wirebox.getInstance( "SQLServerGrammar@qb" );
break;
case "Oracle":
return wirebox.getInstance( "OracleGrammar@qb" );
discoveredGrammar = wirebox.getInstance( "OracleGrammar@qb" );
break;
case "SQLite":
return wirebox.getInstance( "SQLiteGrammar@qb" );
discoveredGrammar = wirebox.getInstance( "SQLiteGrammar@qb" );
break;
default:
return wirebox.getInstance( "BaseGrammar@qb" );
discoveredGrammar = wirebox.getInstance( "BaseGrammar@qb" );
}

return discoveredGrammar;
}

function onMissingMethod( missingMethodName, missingMethodArguments ) {
if ( isNull( variables.grammar ) || !structKeyExists( variables, "grammar" ) ) {
public AutoDiscover function setShouldWrapValues( required boolean shouldWrapValues ) {
variables.shouldWrapValues = arguments.shouldWrapValues;
if ( structKeyExists( variables, "grammar" ) && !isNull( variables.grammar ) ) {
variables.grammar.setShouldWrapValues( arguments.shouldWrapValues );
}
return this;
}

public any function getResolvedGrammar() {
if ( !structKeyExists( variables, "grammar" ) || isNull( variables.grammar ) ) {
variables.grammar = autoDiscoverGrammar();
if ( structKeyExists( variables, "shouldWrapValues" ) && !isNull( variables.shouldWrapValues ) ) {
variables.grammar.setShouldWrapValues( variables.shouldWrapValues );
}
}
return invoke( variables.grammar, missingMethodName, missingMethodArguments );
return variables.grammar;
}

function onMissingMethod( missingMethodName, missingMethodArguments ) {
return invoke( getResolvedGrammar(), missingMethodName, missingMethodArguments );
}

}
Loading
Loading