== QueryAction include::../variables.adoc[] The `*QueryAction*` is the basic action that is used to get records from a {link-table}, generally according to a <>. In SQL/RDBMS terms, it is analogous to a `SELECT` statement, where 0 or more records may be found and returned. === Examples ==== Basic Form [source,java] ---- QueryInput input = new QueryInput(); input.setTableName("orders"); input.setFilter(new QQueryFilter() .withCriteria(new QFilterCriteria("total", GREATER_THAN, new BigDecimal("3.50"))) .withOrderBy(new QFilterOrderBy("orderDate", false))); QueryOutput output = new QueryAction.execute(input); List records = output.getRecords(); ---- === Details `QueryAction`, in general, can be called in two different modes: . The most common use-case case, and default, fetches all records synchronously, does any post-processing (as requested in the <>), and returns all records as a list in the <>). . The alternative use-case is meant for larger operations, where one wouldn't want all records matching a query in-memory. For this scenario, a `RecordPipe` object can be passed in to the <>. This causes `QueryAction` to run its post-processing action on records as they are placed into the pipe, and to potentially block (per the pipe's settings). This method of usage needs to be done on a separate thread from another thread which would be consuming records from the pipe. QQQ's `AsyncRecordPipeLoop` class provides an implementation of doing such a dual-threaded job. If the {link-table} has a `POST_QUERY_CUSTOMIZER` defined, then after records are fetched from the backend, that code is executed on the records before they leave the `QueryAction` (either through its `QueryOutput` or `RecordPipe`). === QueryInput * `table` - *String, Required* - Name of the table being queried against. * `filter` - *<> object* - Specification for what records should be returned, based on *<>* objects, and how they should be sorted, based on *<>* objects. If a `filter` is not given, then all rows in the table will 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 pipe 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 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*. * `shouldFetchHeavyFields` - *boolean, default: true* - Controls whether or not fields marked as `isHeavy` should be fetched & returned or not. * `shouldOmitHiddenFields` - *boolean, default: true* - Controls whether or not fields marked as `isHidden` should be included in the result or not. * `shouldMaskPassword` - *boolean, default: true* - Controls whether or not fields with `type` = `PASSWORD` should be masked, or if their actual values should be returned. * `queryJoins` - *List of <> objects* - Optional list of tables to be joined with the main table being queried. See QueryJoin below for further details. * `fieldNamesToInclude` - *Set of String* - Optional set of field names to be included in the records. ** Fields from a queryJoin must be prefixed by the join table's name or alias, and a period. Field names from the table being queried should not have any sort of prefix. ** A `null` set here (default) means to include all fields from the table and any queryJoins set as select=true. ** An empty set will cause an error, as well any unrecognized field names. ** `QueryAction` will validate the set of field names, and throw an exception if any unrecognized names are given. ** _Note that this is an optional feature, which some backend modules may not implement. Meaning, they would always return all fields._ ==== QQueryFilter A key component of *<>*, 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 <>* - 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 <>* - 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* * `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. [source,java] ---- 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` or `orderBillToCustomer.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) required are 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` or `LESS_THAN_OR_EQUALS` - not `IS_NOT_BLANK` or `IN`). [source,java] .QFilterCriteria definition examples: ---- // in-line, via constructors that take (List values) or (Serializable... values) as 3rd arg new QFilterCriteria("id", IN, 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 two different 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. [source,java] .QFilterOrderBy 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(false); ---- ==== QueryJoin * `joinTable` - *String, required (though inferrable)* - Name of the table that is being joined in to the existing query. ** Will be inferred from *joinMetaData*, if *joinTable* is not set when *joinMetaData* gets set. * `baseTableOrAlias` - *String, required (though inferrable)* - Name of a table (or an alias) already defined in the query, to which the *joinTable* will be joined. ** Will be inferred from *joinMetaData*, if *baseTableOrAlias* is not set when *joinMetaData* gets set (which will only use the leftTableName from the joinMetaData - never an alias). * `joinMetaData` - *QJoinMetaData object* - Optional specification of a {link-join} in the current QInstance. If not set, will be looked up at runtime based on *baseTableOrAlias* and *joinTable*. ** If set before *baseTableOrAlias* and *joinTable*, 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 SQL `FROM` 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 *joinTable* should be selected by the query. If *true*, then the `QRecord` 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. [source,java] .Basic QueryJoin usage example: ---- // selecting from an "orderLine" table, joined to its corresponding (parent) "order" table queryInput.withTableName("orderLine"); queryInput.withQueryJoin(new QueryJoin("order").withSelect(true)); ... queryOutput.getRecords().get(0).getValueBigDecimal("order.grandTotal"); ---- [source,java] ."V" shaped query - selecting from one parent table, and two children joined to it: ---- // TODO this needs verified for accuracy, though is a reasonable starting point as-is // selecting from an "order" table, and two children of it, orderLine and customer queryInput.withTableName("order"); queryInput.withQueryJoin(new QueryJoin("orderLine").withSelect(true)); queryInput.withQueryJoin(new QueryJoin("customer").withSelect(true)); ... QRecord joinedRecord = queryOutput.getRecords().get(0); joinedRecord.getValueString("orderNo"); joinedRecord.getValueString("orderLine.sku"); joinedRecord.getValueString("customer.firstName"); ---- [source,java] ."Chain" shaped query - selecting from one parent table, a child table, and a grandchild: ---- // TODO this needs verified for accuracy, though is a reasonable starting point as-is // selecting from an "order" table, with a "customer" child table, and an "address" sub-table queryInput.withTableName("order"); queryInput.withQueryJoin(new QueryJoin("customer").withSelect(true)); queryInput.withQueryJoin(new QueryJoin("address").withSelect(true)); ... QRecord joinedRecord = queryOutput.getRecords().get(0); joinedRecord.getValueString("orderNo"); joinedRecord.getValueString("customer.firstName"); joinedRecord.getValueString("address.street1"); ---- [source,java] .QueryJoin usage example where two tables have two different joins between them: ---- // TODO this needs verified for accuracy, though is a reasonable starting point as-is // here there's a "fulfillmentPlan" table, which points at the order table (many-to-one, // as an order's plan can change over time, and we keep old plans around). // This join is named: fulfillmentPlanJoinOrder // // The other join is "order" pointing at its current "fulfillmentPlan" // This join is named: orderJoinCurrentFulfillmentPlan // to select an order along with its current fulfillment plan: queryInput.withTableName("order"); queryInput.withQueryJoin(new QueryJoin(instance.getJoin("orderJoinCurrentFulfillmentPlan")) .withSelect(true)); // to select an order, and all fulfillment plans for an order (1 or more records): queryInput.withTableName("order"); queryInput.withQueryJoin(new QueryJoin(instance.getJoin("fulfillmentPlanJoinOrder")) .withSelect(true)); ---- [source,java] .QueryJoin usage example for table with two joins to the same child table, selecting from both: ---- // 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. // we must also define an alias for each of the QueryJoins queryInput.withTableName("order"); 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") + " paid for an order, to be sent to " + record.getValueString("shipToCustomer.firstName") ---- [source,java] .Implicit QueryJoin, where unambiguous and required by QQueryFilter ---- // TODO finish and verify queryInput.withTableName("order"); ---- === 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 an `IllegalStateException` being thrown - as the records were placed into the pipe as they were fetched, and cannot all be accessed as a single list._