Introduction
QQQ is …
-
Framework
-
Declarative
-
Easy thing easy; Hard thing possible
-
Customizable
Meta Data
QQQ Tables
The core type of object in a QQQ Instance is the Table. In the most common use-case, a QQQ Table may be the in-app representation of a Database table. That is, it is a collection of records (or rows) of data, each of which has a set of fields (or columns).
QQQ also allows other types of data sources (QQQ Backends) to be used as tables, such as File systems, API’s, Java enums or objects, etc. All of these backend types present the same interfaces (both user-interfaces, and application programming interfaces), regardless of their backend type.
QTableMetaData
Tables are defined in a QQQ Instance in a QTableMetaData
object.
All tables must reference a QQQ Backend, a list of fields that define the shape of records in the table, and additional data to describe how to work with the table within its backend.
QTableMetaData Properties:
-
name
- String, Required - Unique name for the table within the QQQ Instance. -
label
- String - User-facing label for the table, presented in User Interfaces. Inferred fromname
if not set. -
backendName
- String, Required - Name of a QQQ Backend in which this table’s data is managed. -
fields
- Map of String → QQQ Field, Required - The columns of data that make up all records in this table. -
primaryKeyField
- String, Conditional - Name of a QQQ Field that serves as the primary key (e.g., unique identifier) for records in this table. -
uniqueKeys
- List of UniqueKey - Definition of additional unique constraints (from an RDBMS point of view) from the table. e.g., sets of columns which must have unique values for each record in the table. -
backendDetails
- QTableBackendDetails or subclass - Additional data to configure the table within its QQQ Backend. -
automationDetails
- QTableAutomationDetails - Configuration of automated jobs that run against records in the table, e.g., upon insert or update. -
customizers
- Map of String → QCodeReference - References to custom code that are injected into standard table actions, that allow applications to customize certain parts of how the table works. -
parentAppName
- String - Name of a QQQ App that this table exists within. -
icon
- QIcon - Icon associated with this table in certain user interfaces. -
recordLabelFormat
- String - Java Format String, used withrecordLabelFields
to produce a label shown for records from the table. -
recordLabelFields
- List of String, Conditional - Used withrecordLabelFormat
to provide values for any format specifiers in the format string. These strings must be field names within the table.-
Example of using
recordLabelFormat
andrecordLabelFields
:
-
// given these fields in the table:
new QFieldMetaData("name", QFieldType.STRING)
new QFieldMetaData("birthDate", QFieldType.DATE)
// We can produce a record label such as "Darin Kelkhoff (1980-05-31)" via:
.withRecordLabelFormat("%s (%s)")
.withRecordLabelFields(List.of("name", "birthDate"))
-
sections
- List of QFieldSection - Mechanism to organize fields within user interfaces, into logical sections. If any sections are present in the table meta data, then all fields in the table must be listed in exactly 1 section. If no sections are defined, then instance enrichment will define default sections. -
associatedScripts
- List of AssociatedScript - Definition of user-defined scripts that can be associated with records within the table. -
enabledCapabilities
anddisabledCapabilities
- Set of Capability enum values - Overrides from the backend level, for capabilities that this table does or does not possess.
QQQ Reports
QQQ can generate reports based on QQQ Tables defined within a QQQ Instance. Users can run reports, providing input values. Alternatively, application code can run reports as needed, supplying input values.
QReportMetaData
Reports are defined in a QQQ Instance with a QReportMetaData
object.
Reports are defined in terms of their sources of data (QReportDataSource
), and their view(s) of that data (QReportView
).
QReportMetaData Properties:
-
name
- String, Required - Unique name for the report within the QQQ Instance. -
label
- String - User-facing label for the report, presented in User Interfaces. Inferred fromname
if not set. -
processName
- String - Name of a QQQ Process used to run the report in a User Interface. -
inputFields
- List of QQQ Field - Optional list of fields used as input to the report.-
The values in these fields can be used via the syntax
${input.NAME}
, whereNAME
is thename
attribute of theinputField
. -
For example:
-
// given this inputField:
new QFieldMetaData("storeId", QFieldType.INTEGER)
// its run-time value can be accessed, e.g., in a query filter under a data source:
new QFilterCriteria("storeId", QCriteriaOperator.EQUALS, List.of("${input.storeId}"))
// or in a report view's title or field formulas:
.withTitleFields(List.of("${input.storeId}"))
new QReportField().withName("storeId").withFormula("${input.storeId}")
-
dataSources
- List of QReportDataSource, Required - Definitions of the sources of data for the report. At least one is required.
QReportDataSource
Data sources for QQQ Reports can either reference QQQ Tables within the QQQ Instance, or they can provide custom code in the form of a CodeReference
to a Supplier
, for use cases such as a static data tab in an Excel report.
QReportDataSource Properties:
-
name
- String, Required - Unique name for the data source within its containing Report. -
sourceTable
- String, Conditional - Reference to a QQQ Table in the QQQ Instance, which the data source queries data from. -
queryFilter
- QQueryFilter - If asourceTable
is defined, then the filter specified here is used to filter and sort the records queried from that table when generating the report. -
staticDataSupplier
- QCodeReference, Conditional - Reference to custom code which can be used to supply the data for the data source, as an alternative to querying asourceTable
.-
Must be a
JAVA
code type -
Must be a
REPORT_STATIC_DATA_SUPPLIER
code usage. -
The referenced class must implement the interface:
Supplier<List<List<Serializable>>>
.
-
QReportView
Report Views control how the source data for a report is organized and presented to the user in the output report file.
If a DataSource describes the rows for a report (e.g., what table provides what records), then a View may be thought of as describing the columns in the report.
A single report can have multiple views, specifically, for the use-case where an Excel file is being generated, in which case each View creates a tab or sheet within the xlsx
file.
QReportView Properties:
-
name
- String, Required - Unique name for the view within its containing Report. -
label
- String - Used as a sheet (tab) label in Excel formatted reports. -
type
- enum of TABLE, SUMMARY, PIVOT. Required - Defines the type of view being defined.-
TABLE views are a simple listing of the records from the data source.
-
SUMMARY views are essentially pre-computed Pivot Tables. That is to say, the aggregation done by a Pivot Table in a spreadsheet file is done by QQQ while generating the report. In this way, a non-spreadsheet report (e.g., PDF or CSV) can have summarized data, as though it were a Pivot Table in a live spreadsheet.
-
PIVOT views produce actual Pivot Tables, and are only supported in Excel files (and are not supported at the time of this writing).
-
-
dataSourceName
- String, Required - Reference to a DataSource within the report, that is used to provide the rows for the view. -
varianceDataSourceName
- String - Optional reference to a second DataSource within the report, that is used inSUMMARY
type views for computing variances.-
For example, given a Data Source with a filter that selects all sales records for a given year, a Variance Data Source may have a filter that selects the previous year, for doing comparissons.
-
-
pivotFields
- List of String, Conditional - For SUMMARY or PIVOT type views, specify the field(s) used as pivot rows.-
For example, in a summary view of orders, you may "pivot" on the customerId field, to produce one row per-customer, with aggregate data for that customer.
-
-
titleFormat
- String - Java Format String, used withtitleFields
(if given), to produce a title row, e.g., first row in the view (before any rows from the data source). -
titleFields
- List of String, Conditional - Used withtitleFormat
, to provide values for any format specifiers in the format string. Syntax to reference a field (e.g., from a report input field) is:${input.NAME}
, whereNAME
is thename
attribute of the inputField.-
Example of using
titleFormat
andtitleFields
:
-
// given these inputFields:
new QFieldMetaData("startDate", QFieldType.DATE)
new QFieldMetaData("endDate", QFieldType.DATE)
// a view can have a title row like this:
.withTitleFormat("Weekly Sales Report - %s - %s")
.withTitleFields(List.of("${input.startDate}", "${input.endDate}"))
-
includeHeaderRow
- boolean, default true - Indication that first row of the view should be the column labels.-
If true, then header row is put in the view.
-
If false, then no header row is put in the view.
-
-
includeTotalRow
- boolean, default false - Indication that a totals row should be added to the view. All numeric columns are summed to produce values in the totals row.-
If true, then totals row is put in the view.
-
If false, then no totals row is put in the view.
-
-
includePivotSubTotals
- boolean, default false - For a SUMMARY or PIVOT type view, if there are more than 1 pivotFields being used, this field is an indication that each higher-level pivot should include sub-totals.-
TODO - provide example
-
-
columns
- List of QReportField, required - Definition of the columns to appear in the view. See section on QReportField for details. -
orderByFields
- List of QFilterOrderBy, optional - For a SUMMARY or PIVOT type view, how to sort the rows. -
recordTransformStep
- QCodeReference, subclass ofAbstractTransformStep
- Custom code reference that can be used to transform records after they are queried from the data source, and before they are placed into the view. Can be used to transform or customize values, or to look up additional values to add to the report.-
TODO - provide example
-
-
viewCustomizer
- QCodeReference, implementation of interfaceFunction<QReportView, QReportView>
- Custom code reference that can be used to customize the report view, at runtime. Can be used, for example, to dynamically define the report’s columns.-
TODO - provide example
-
QReportField
Actions
QueryAction
The QueryAction
is the basic action that is used to get records from a QQQ Table.
In SQL/RDBMS terms, it is analogous to a SELECT
statement, where 0 or more records may be found and returned.
Examples
Simplest Form
QueryInput input = new QueryInput(qInstance);
input.setSession(session);
input.setTableName("orders");
input.setFilter(new QQueryFilter(new QFilterCriteria("total", GREATER_THAN, new BigDecimal("3.50"))));
QueryOutput output = new QueryAction.execute(input);
List<QRecord> records = output.getRecords();
QueryInput
-
table
- String, Required - Name of the table being queried against. -
filter
- QQueryFilter object - Specification for what records should be returned, based on QFilterCriteria objects, and how they should be sorted, based on QFilterOrderBy objects. -
skip
- Integer - Optional number of records to be skipped at the beginning of the result set. e.g., for implementing pagination. -
limit
- Integer - Optional maximum number of records to be returned by the query. -
transaction
- QBackendTransaction object - Optional transaction object.-
Behavior for this object is backend-dependant. In an RDBMS backend, this object is generally needed if you want your query to see data that may have been modified within the same transaction.
-
-
recordPipe
- RecordPipe object - Optional object that records are placed into, for asynchronous processing.-
If a recordPipe is used, then records cannot be retrieved from the QueryOutput. Rather, such records must be read from the pipe’s
consumeAvailableRecords()
method. -
A recordPipe should only be used when a QueryAction is running in a separate Thread from the record’s consumer.
-
-
shouldTranslatePossibleValues
- boolean, default: false - Controls whether any fields in the table with a possibleValueSource assigned to them should have those possible values looked up (e.g., to provide text translations in the generated records'displayValues
map).-
For example, if running a query to present results to a user, this would generally need to be true. But if running a query to provide data as part of a process, then this can generally be left as false.
-
-
shouldGenerateDisplayValues
- boolean, default: false - Controls whether if field level displayFormats should be used to populate the generated records'displayValues
map.-
For example, if running a query to present results to a user, this would generally need to be true. But if running a query to provide data as part of a process, then this can generally be left as false.
-
-
queryJoins
- List of QueryJoin objects - Optional list of tables to be joined with the main table specified in the QueryInput. See QueryJoin below for further details.
QQueryFilter
A key component of QueryInput, a QQueryFilter defines both what records should be included in a query’s results (e.g., an SQL WHERE
), as well as how those results should be sorted (SQL ORDER BY
).
-
criteria
- List of QFilterCriteria - Individual conditions or clauses to filter records. They are combined using the booleanOperator specified in the QQueryFilter. See below for further details. -
orderBys
- List of QFilterOrderBy - List of fields (and directions) to control the sorting of query results. In general, multiple orderBys can be given (depending on backend implementations). -
booleanOperator
- Enum of AND, OR, default: AND - Specifies the logical joining operator used among individual criteria. -
subFilters
- List of QQueryFilter - To build arbitrarily complex queries, with nested boolean logic, 0 or more subFilters may be provided.-
Each subFilter can include its own additional subFilters.
-
Each subFilter can specify a different booleanOperator.
-
For example, consider the following QQueryFilter, that uses two subFilters, and a mix of booleanOperators
-
queryInput.setFilter(new QQueryFilter()
.withBooleanOperator(OR)
.withSubFilters(List.of(
new QQueryFilter().withBooleanOperator(AND)
.withCriteria(new QFilterCriteria("firstName", EQUALS, "James"))
.withCriteria(new QFilterCriteria("lastName", EQUALS, "Maes")),
new QQueryFilter().withBooleanOperator(AND)
.withCriteria(new QFilterCriteria("firstName", EQUALS, "Darin"))
.withCriteria(new QFilterCriteria("lastName", EQUALS, "Kelkhoff"))
)));
// which would generate the following WHERE clause in an RDBMS backend:
WHERE (first_name='James' AND last_name='Maes') OR (first_name='Darin' AND last_name='Kelkhoff')
QFilterCriteria
-
fieldName
- String, required - Reference to a field on the table being queried.-
Or, in the case of a query with queryJoins, a qualified name of a field from a join-table (where the qualifier would be the joined table’s name or alias, followed by a dot)
-
For example:
orderLine.sku
ororderBillToCustomer.firstName
-
-
-
operator
- Enum of QCriteriaOperator, required - Comparison operation to be applied to the field specified as fieldName and the values or otherFieldName.-
e.g.,
EQUALS
,NOT_IN
,GREATER_THAN
,BETWEEN
,IS_BLANK
, etc.
-
-
values
- List of values, conditional - Provides the value(s) that the field is compared against. The number of values (0, 1, 2, or more) be driven based on the operator being used. If an otherFieldName is given, and the operator expects 1 value, then values is ignored, and otherFieldName is used. -
otherFieldName
- String, conditional - Specifies that the fieldName should be compared against another field in the records, rather than the values in the values property. Only used for operators that expect 1 value (e.g.,EQUALS
orLESS_THAN_OR_EQUALS
- notIS_NOT_BLANK
orIN
).
QFilterCriteria definition examples:
// one-liners, via constructors that take (List<Serializable> values) or (Serializable... values) in 3rd position
new QFilterCriteria("id", IN, List.of(1, 2, 3))
new QFilterCriteria("name", IS_BLANK)
new QFilterCriteria("orderNo", IN, orderNoList)
new QFilterCriteria("state", EQUALS, "MO");
// long-form, with fluent setters
new QFilterCriteria()
.withFieldName("quantity")
.withOpeartor(QCriteriaOperator.GREATER_THAN)
.withValues(List.of(47));
// to use otherFieldName, long-form must be used
new QFilterCriteria()
.withFieldName("firstName")
.withOpeartor(QCriteriaOperator.EQUALS)
.withOtherFieldName("lastName");
// using otherFieldName to build a criterion that looks at two fields from join tables
new QFilterCriteria()
.withFieldName("billToCustomer.lastName")
.withOpeartor(QCriteriaOperator.NOT_EQUALS)
.withOtherFieldName("shipToCustomer.lastName");
QFilterOrderBy
-
fieldName
- String, required - Reference to a field on the table being queried.-
Or, in the case of a query with queryJoins, a qualified name of a field from a join-table (where the qualifier would be the joined table’s name or alias, followed by a dot)
-
-
isAscending
- boolean, default: true - Specify if the sort is ascending or descending.
QFilterCriteria definition examples:
// short-form, via constructors
new QFilterOrderBy("id") // isAscending defaults to true.
new QFilterOrderBy("name", false)
// long-form, with fluent setters
new QFilterOrderBy()
.withFieldName("birthDate")
.withIsAscending(true);
QueryJoin
-
leftTableOrAlias
- String, required - Name of the table on the left side of the join. If the table to be used here was given an alias from a previous queryJoin, then that alias name should be given here.-
Will be inferred from joinMetaData, if leftTableOrAlias is not set when joinMetaData gets set (which will only use the leftTableName from the joinMetaData - never an alias)
-
-
rightTable
- String, required - Name of the table on the right side of the join.-
Will be inferred from joinMetaData, if rightTable is not set when joinMetaData gets set.
-
-
joinMetaData
- QJoinMetaData object - Optional specification of a QQQ Join in the current QInstance. If not set, will be looked up at runtime based on leftTableOrAlias and rightTable.-
If set before leftTableOrAlias and rightTable, then they will be set based on the leftTable and rightTable in this object.
-
-
alias
- String - Optional (unless multiple instances of the same table are being joined together, when it becomes required). Behavior based on SQLFROM
clause aliases. If given, must be used as the part before the dot in field name specifications throughout the rest of the query input. -
select
- boolean, default: false - Specify whether fields from the rightTable should be selected by the query. If true, then theQRecord
objects returned by this query will have values with corresponding to the (table-or-alias.
field-name) form. -
type
- Enum of INNER, LEFT, RIGHT, FULL, default: INNER - specifies the SQL-style type of join being performed.
QueryJoin definition examples:
// selecting from an "orderLine" table joined to its corresponding "order" table
queryInput.withQueryJoin(new QueryJoin("orderLine", "order").withSelect(true));
...
queryOutput.getRecords().get(0).getValueBigDecimal("order.grandTotal");
// given an "order" table with 2 foreign keys to a customer table (billToCustomerId and shipToCustomerId)
// Note, we must supply the JoinMetaData to the QueryJoin, to drive what fields to join on in each case.
queryInput.withQueryJoins(List.of(
new QueryJoin(instance.getJoin("orderJoinShipToCustomer")
.withAlias("shipToCustomer")
.withSelect(true)),
new QueryJoin(instance.getJoin("orderJoinBillToCustomer")
.withAlias("billToCustomer")
.withSelect(true))));
...
record.getValueString("billToCustomer.firstName")
+ " placed an order for "
+ record.getValueString("shipToCustomer.firstName")
QueryOutput
-
records
- List of QRecord - List of 0 or more records that match the query filter.-
Note: If a recordPipe was supplied to the QueryInput, then calling
queryOutput.getRecords()
will result in anIllegalStateException
being thrown - as the records were placed into the pipe as they were fetched, and cannot all be accessed as a single list.
-
RenderTemplateAction
The RenderTemplateAction
performs the job of taking a template - that is, a string of code, in a templating language, such as Velocity, and merging it with a set of data (known as a context), to produce some using-facing output, such as a String of HTML.
Examples
Canonical Form
RenderTemplateInput input = new RenderTemplateInput(qInstance);
input.setSession(session);
input.setCode("Hello, ${name}");
input.setTemplateType(TemplateType.VELOCITY);
input.setContext(Map.of("name", "Darin"));
RenderTemplateOutput output = new RenderTemplateAction.execute(input);
String result = output.getResult();
assertEquals("Hello, Darin", result);
Convenient Form
String result = RenderTemplateAction.renderVelocity(input, Map.of("name", "Darin"), "Hello, ${name}");
assertEquals("Hello, Darin", result);
RenderTemplateInput
-
code
- String, Required - String of template code to be rendered, in the templating language specified by thetype
parameter. -
type
- Enum of VELOCITY, Required - Specifies the language of the template code. -
context
- Map of String → Object - Data to be made available to the template during rendering.
RenderTemplateOutput
-
result
- String - Result of rendering the input template and context.