BoxLang 🚀 A New JVM Dynamic Language Learn More...
qb is a fluent query builder for CFML. It is heavily inspired by Eloquent from Laravel.
Using qb, you can:
Installation is easy through CommandBox
and ForgeBox. Simply
type box install qb to get started.
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:
// 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.
Compare these two examples:
// Plain old CFML
q = queryExecute("SELECT * FROM users");
// qb
query = wirebox.getInstance('QueryBuilder@qb');
q = query.from('users').get();
The differences become even more stark when we introduce more complexity:
// Plain old CFML
q = queryExecute(
"SELECT * FROM posts WHERE published_at IS NOT NULL AND author_id IN ?",
[ { value = '5,10,27', cfsqltype = 'NUMERIC', list = true } ]
);
// qb
query = wirebox.getInstance('QueryBuilder@qb');
q = query.from('posts')
.whereNotNull('published_at')
.whereIn('author_id', [5, 10, 27])
.get();
With Quick you can easily handle setting order by statements before the columns you want or join statements after a where clause:
query = wirebox.getInstance('QueryBuilder@qb');
q = query.from('posts')
.orderBy('published_at')
.select('post_id', 'author_id', 'title', 'body')
.whereLike('author', 'Ja%')
.join('authors', 'authors.id', '=', 'posts.author_id')
.get();
// Becomes
q = queryExecute(
"SELECT post_id, author_id, title, body FROM posts INNER JOIN authors ON authors.id = posts.author_id WHERE author LIKE ? ORDER BY published_at",
[ { value = 'Ja%', cfsqltype = 'VARCHAR', list = false, null = false } ]
);
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!
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.
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:
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:
OPENJSON; database
compatibility level 130+ is requiredJSONB_ARRAY_ELEMENTS_TEXT
JSON_TABLE
JSON_TABLE
Derby does not support bulk value expansion and throws an
UnsupportedOperation exception for non-empty collections.
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:
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.
qb includes named return formatters for array,
query, none, and struct. The
struct formatter returns a struct of rows keyed by a
selected column:
usersByUsername = query
.setReturnFormat( "struct", { "columnKey": "username" } )
.from( "users" )
.get();
Applications can register reusable custom formatter factories in their qb module settings:
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
To use the SQLite grammar for qb you will need to setup a datasource that connects to a SQLite database.
Download the latest release of the SQLite JDBC Driver i.e. https://github.com/xerial/sqlite-jdbc/releases/download/3.40.0.0/sqlite-jdbc-3.40.0.0.jar
Drop it in the /lib directory
Configure the application to load the library by adding this line
in your Application.cfc file.
this.javaSettings = { loadPaths : [ ".\lib" ] };
Restart the server
You can configure your datasource for Lucee or Adobe Coldfusion using the steps below. You can also use cfconfig with CommandBox to do it automatically for you.
For both Lucee and ACF you need to set the JDBC Driver class to
org.sqlite.JDBC. Then you need to specify the JDBC
connection string as jdbc:sqlite: <your database
path>. i.e. jdbc:sqlite: C:/data/my_database.db
Lucee
org.sqlite.JDBC for Classjdbc:sqlite: <db path>
ACF
other for the datasource driverorg.sqlite.JDBC for the Driver Classorg.sqlite.JDBC for the Driver Namejdbc:sqlite: <db path>
You can browse the full documentation at https://qb.ortusbooks.com
returning in an upsert clause
(0245dd1)whereBetween statements
(0645aff)isExpression check
(4adcad1)whereSub from inside whereColumn (3bf7ec9)GROUP BY and ORDER BY clauses
(9abac2b)isBuilder checks on BoxLang (bb1b712)duplicate calls (0ae195b)FOR clause to last position (cfed85d)convertEmptyStringsToNull constructor argument (d08e237)tableName in exists aggregate queries (f9290d2)limit( 1 ) to exists aggregates
(2d90588)name as a valid query param key
(d2a6e90)autoAddScale setting (db178d1)strictDateDetection setting (c3cb496)autoDeriveNumericType setting (3259cf3)max, min, count, and sum (66ddd2e)sqltype alongside cfsqltype. (a13a54f)convertEmptyStringsToNull flag. (021a432)convertEmptyStringsToNull to true
(7836576)CREATE TABLE ... AS and SELECT ... INTO using schema.createAs method (1e138c1)returningAll() method — shortcut for returning( "*" ) (1ae7521)jsonb() support (434c2c8)JSON type for json() columns (6784932).orderByRandom() method (a2f6698)truncate method (d756709)upsert (d5c0ac7)chunk and paginate methods (12b7bfd)DerbyGrammar
(882aa7b)UUID type for guid() (3b8f89f)BaseGrammar (c62e301)isDefined for BoxLang compatibility
(745023e)upsert (50fa99a)parseNumber function for ACF
(b887a67)clone()
(dcf87de)withAlias (9c52313)from (2b23fbb)addBindings public
(7193bb4)findOrFail and existsOrFail methods
(96b9047)defaultSchema (697a307)having bindings from where bindings
(7661752)returntypes (63a1599)toSQL and dump (379d1e7)newQuery and withReturnFormat (20416c3)defaultOptions when calling newQuery (4e713d4)autoDeriveNumericType default
(0341edb)autoDeriveNumericType by default (295798b)sumRaw method (ee86423)moduleSettings (a98fb6c)value and values (60d131e)scale to bindings when needed (0a92cea)value and values (e4c16b8)andWhere methods
(7273ce4)last()
(5b0fe28)toSQL would modify the builder object. (c00ecef)whereIn
(d0cc901)subSelect method (79343a0)with methods.
(9b946c4)isInstanceOf
(6388bfd)forPage arguments
(0037cdd)returningArrays in favor of returnFormat (f52e25a)selectRaw helper method.
Alias table for `from.
(20da7ea)toBeWithCase for SQL statement checks. Add a test about uppercasing Oracle wrapped values.
(e14da32)extractBinding
(80dc5b0)gulp watch instead for BrowserSync.) :-)
(623517f)moduleSettings (a98fb6c)value and values (60d131e)value and values (e4c16b8)andWhere methods
(7273ce4)Add CTE support for the with CTE AS (...) syntax (3e10da6)
Fixed JoinClause.newQuery() to expect QueryBuilder object as return value (#48) (5d113c7)
Remove period as it is not needed for a single sentance (87347c7)
Add CTE support for the with CTE AS (...) syntax (3e10da6)
Since some users have access to multiple schemas on the same database,
allow an optional schema parameter passed to hasTable and hasColumn
(9bfcd45)
andWhere behaves exactly like where. It is provided for
a more readable method chain if desired.
(309f4d8)
Add AutoDiscover component to allow for database discovery at runtime as opposed to just at module registration. (700948a)
By default, we will auto discover the grammar for the user. This only happens once for ColdBox modules, so the database hit should be minimal. If the user specifies a grammar in their settings, we will use that and not even try to detect the grammar. (b2347ae)
Full QueryBuilder and SchemaBuilder support for all four database grammars (MSSQL, MySQL, Oracle, and Postgres). Revamped test suite to have consistent grammar test coverage. (733dae3)
compileDropAllObjects needs to be implemented in every Grammar. By default, it throws an exception. Only a MySQLGrammar implementation currently exists. (c3e23b5)
Fixed JoinClause.newQuery() to expect QueryBuilder object as return value (#48) (5d113c7)
(680750a)
last()
(5b0fe28)Includes refactor for TableIndex to always deal with multiple columns. (61306e0)
MySQL has an unfortunate syntax that requires the definition to be repeated. We may be able to discover this from the table, but right now we're punting and asking the user to redeclare the column definition.
Fun fact, in MySQL, renameColumn will let you modifyColumn at the same time. (0bb926e)
Also refactor SchemaCommands to a component that can take arbitrary parameters. (f7a7fce)
(f8940bc)
(8b175c7)
(b77781a)
Since build should be overridden less often than options, make it last. (3ee5dca)
Fits better with our current documentation and ModuleConfig.cfc settings (1bd2dcb)
Still needs index creation. (735a03a)
*: Add column modifiers — comment, default, nullable, unsigned (25fbade)
*: Add uuid type (f35e1f1)
*: Add big, medium, small, and tiny integer and increments variants. (2bb379d)
*: Add medium and long text types (35b7d83)
*: Add json type (alias to TEXT) (6403d3f)
*: Add float type (86cc974)
*: Add enum type. (e2f17ab)
*: Add decimal type (aa13c72)
*: Add bit type (48d0044)
*: Have boolean be it's own type so different grammars can interpolate it differently. (c909f9f)
*: Add date, datetime, time, and timestamp types. (857bdcf)
*: Add char and string types (5732161)
*: Add integer, unsignedInteger, increments, and text types (fb76853)
*: Add more column types for schema builder
bigIncrements
bigInteger
boolean
tinyInteger
unsignedBigInteger (3f80002)
*: Initial Schema Buidler implementation
Move Grammars from being nested inside Query to it's own top-level folder.
Rename Builder to QueryBuilder.
Create SchemaBuilder, Column, and TableIndex and three basic tests.
(8a299f6)
toSQL would modify the builder object.Affected debugging and things like updateOrInsert where the update call is preceded by an exists call.
(c00ecef)
Use the withReturnFormat( "array" ) to get around inconsistencies with queries across CFML engines.
(17afdfa)
whereIn
(d0cc901)Closes #17 (2edaf30)
subSelect methodCloses #18 (79343a0)
Perfect for logging all queries that are executed!
interceptData includes: sql, bindings, and options.
(0c964e5)
Closes #20. (4b46fce)
Remove a couple blank lines. (b77a87b)
Commit them for now until commandbox-docbox is fixed and we can do it in Travis. (fa2edae)
with methods.
(9b946c4)isInstanceOf
(6388bfd)forPage arguments
(0037cdd)returningArrays in favor of returnFormatreturnFormat can take a closure or “array” or “query”.
Aggregate methods correctly ignore returnFormat
Fixes #6, #7 (f52e25a)
selectRaw helper method.
Alias table for `from.
(20da7ea)4 spaces for indentation and spaces inside braces with arguments ({}) (73f0856)
toBeWithCase for SQL statement checks. Add a test about uppercasing Oracle wrapped values.
(e14da32)Quick will be the ORM implementation that will use qb underneath the hood. (29b34af)
extractBinding
(80dc5b0)gulp watch instead for BrowserSync.) :-)
(623517f)normalizeToArray handles the case where variadic arguments are passed in. This comes at a cost, about 50 ms.
Speed is everything when testing against a database. (d54bcce)
isInstanceOf takes about 30-40 ms per column. For just one table with
6 columns, this is close to a quarter of a second. This adds up.
Instead, just checking if the variable is an object that has a getSQL
key (which we assume is a method), we save all of that time.
(15042ce)
$
box install qb