Skip to content

Commit ec71355

Browse files
Merge pull request #157 from Kingsrook/feature/support-CE-2257-ice-logic
Feature/support ce 2257 ice logic
2 parents 38a17b2 + 53f4833 commit ec71355

File tree

26 files changed

+2003
-54
lines changed

26 files changed

+2003
-54
lines changed

docs/actions/QueryAction.adoc

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,31 +155,88 @@ new QFilterOrderBy()
155155
----
156156

157157
==== QueryJoin
158-
* `joinTable` - *String, required* - Name of the table that is being joined in to the existing query.
158+
* `joinTable` - *String, required (though inferrable)* - Name of the table that is being joined in to the existing query.
159159
** Will be inferred from *joinMetaData*, if *joinTable* is not set when *joinMetaData* gets set.
160-
* `baseTableOrAlias` - *String, required* - Name of a table (or an alias) already defined in the query, to which the *joinTable* will be joined.
160+
* `baseTableOrAlias` - *String, required (though inferrable)* - Name of a table (or an alias) already defined in the query, to which the *joinTable* will be joined.
161161
** 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).
162162
* `joinMetaData` - *QJoinMetaData object* - Optional specification of a {link-join} in the current QInstance.
163163
If not set, will be looked up at runtime based on *baseTableOrAlias* and *joinTable*.
164164
** If set before *baseTableOrAlias* and *joinTable*, then they will be set based on the *leftTable* and *rightTable* in this object.
165165
* `alias` - *String* - Optional (unless multiple instances of the same table are being joined together, when it becomes required).
166166
Behavior based on SQL `FROM` clause aliases.
167167
If given, must be used as the part before the dot in field name specifications throughout the rest of the query input.
168-
* `select` - *boolean, default: false* - Specify whether fields from the *rightTable* should be selected by the query.
168+
* `select` - *boolean, default: false* - Specify whether fields from the *joinTable* should be selected by the query.
169169
If *true*, then the `QRecord` objects returned by this query will have values with corresponding to the (table-or-alias `.` field-name) form.
170170
* `type` - *Enum of INNER, LEFT, RIGHT, FULL, default: INNER* - specifies the SQL-style type of join being performed.
171171

172172
[source,java]
173-
.QueryJoin definition examples:
173+
.Basic QueryJoin usage example:
174174
----
175-
// selecting from an "orderLine" table - then join to its corresponding "order" table
175+
// selecting from an "orderLine" table, joined to its corresponding (parent) "order" table
176176
queryInput.withTableName("orderLine");
177177
queryInput.withQueryJoin(new QueryJoin("order").withSelect(true));
178178
...
179179
queryOutput.getRecords().get(0).getValueBigDecimal("order.grandTotal");
180+
----
181+
182+
[source,java]
183+
."V" shaped query - selecting from one parent table, and two children joined to it:
184+
----
185+
// TODO this needs verified for accuracy, though is a reasonable starting point as-is
186+
// selecting from an "order" table, and two children of it, orderLine and customer
187+
queryInput.withTableName("order");
188+
queryInput.withQueryJoin(new QueryJoin("orderLine").withSelect(true));
189+
queryInput.withQueryJoin(new QueryJoin("customer").withSelect(true));
190+
...
191+
QRecord joinedRecord = queryOutput.getRecords().get(0);
192+
joinedRecord.getValueString("orderNo");
193+
joinedRecord.getValueString("orderLine.sku");
194+
joinedRecord.getValueString("customer.firstName");
195+
----
196+
197+
[source,java]
198+
."Chain" shaped query - selecting from one parent table, a child table, and a grandchild:
199+
----
200+
// TODO this needs verified for accuracy, though is a reasonable starting point as-is
201+
// selecting from an "order" table, with a "customer" child table, and an "address" sub-table
202+
queryInput.withTableName("order");
203+
queryInput.withQueryJoin(new QueryJoin("customer").withSelect(true));
204+
queryInput.withQueryJoin(new QueryJoin("address").withSelect(true));
205+
...
206+
QRecord joinedRecord = queryOutput.getRecords().get(0);
207+
joinedRecord.getValueString("orderNo");
208+
joinedRecord.getValueString("customer.firstName");
209+
joinedRecord.getValueString("address.street1");
210+
----
211+
212+
[source,java]
213+
.QueryJoin usage example where two tables have two different joins between them:
214+
----
215+
// TODO this needs verified for accuracy, though is a reasonable starting point as-is
216+
// here there's a "fulfillmentPlan" table, which points at the order table (many-to-one,
217+
// as an order's plan can change over time, and we keep old plans around).
218+
// This join is named: fulfillmentPlanJoinOrder
219+
//
220+
// The other join is "order" pointing at its current "fulfillmentPlan"
221+
// This join is named: orderJoinCurrentFulfillmentPlan
222+
223+
// to select an order along with its current fulfillment plan:
224+
queryInput.withTableName("order");
225+
queryInput.withQueryJoin(new QueryJoin(instance.getJoin("orderJoinCurrentFulfillmentPlan"))
226+
.withSelect(true));
227+
228+
// to select an order, and all fulfillment plans for an order (1 or more records):
229+
queryInput.withTableName("order");
230+
queryInput.withQueryJoin(new QueryJoin(instance.getJoin("fulfillmentPlanJoinOrder"))
231+
.withSelect(true));
232+
----
180233

234+
[source,java]
235+
.QueryJoin usage example for table with two joins to the same child table, selecting from both:
236+
----
181237
// given an "order" table with 2 foreign keys to a customer table (billToCustomerId and shipToCustomerId)
182238
// Note, we must supply the JoinMetaData to the QueryJoin, to drive what fields to join on in each case.
239+
// we must also define an alias for each of the QueryJoins
183240
queryInput.withTableName("order");
184241
queryInput.withQueryJoins(List.of(
185242
new QueryJoin(instance.getJoin("orderJoinShipToCustomer")
@@ -190,11 +247,18 @@ queryInput.withQueryJoins(List.of(
190247
.withSelect(true))));
191248
...
192249
record.getValueString("billToCustomer.firstName")
193-
+ " placed an order for "
250+
+ " paid for an order, to be sent to "
194251
+ record.getValueString("shipToCustomer.firstName")
195252
196253
----
197254

255+
[source,java]
256+
.Implicit QueryJoin, where unambiguous and required by QQueryFilter
257+
----
258+
// TODO finish and verify
259+
queryInput.withTableName("order");
260+
----
261+
198262
=== QueryOutput
199263
* `records` - *List of QRecord* - List of 0 or more records that match the query filter.
200264
** _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._
-64.8 KB
Binary file not shown.

docs/metaData/Fields.adoc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,37 @@ if the value in the field is longer than the `maxLength`, then one of the follow
5656
5757
----
5858

59+
===== ValueRangeBehavior
60+
Used on Numeric fields. Specifies min and/or max allowed values for the field.
61+
For each of min and max, the following attributes can be set:
62+
63+
* `minValue` / `maxValue` - the number that is the limit.
64+
* `minAllowEqualTo` / `maxAllowEqualTo` - boolean (default true). Controls if < (>) or ≤ (≥).
65+
* `minBehavior` / `maxBehavior` - enum of `ERROR` (default) or `CLIP`.
66+
** If `ERROR`, then a value not within the range causes an error, and the value does not get stored.
67+
** else if `CLIP`, then a value not within the range gets "clipped" to either be the min/max (if allowEqualTo),
68+
or to the min/max plus/minus the clipAmount
69+
* `minClipAmount` / `maxClipAmount` - Default 1. Used when behavior is `CLIP` (only applies when
70+
not allowEqualTo).
71+
72+
[source,java]
73+
.Examples of using ValueRangeBehavior
74+
----
75+
new QFieldMetaData("noOfShoes", QFieldType.INTEGER)
76+
.withBehavior(new ValueRangeBehavior().withMinValue(0));
77+
78+
new QFieldMetaData("price", QFieldType.BIG_DECIMAL)
79+
.withBehavior(new ValueRangeBehavior()
80+
// set the min value to be >= 0, and an error if an input is < 0.
81+
.withMinValue(BigDecimal.ZERO)
82+
.withMinAllowEqualTo(true)
83+
.withMinBehavior(ERROR)
84+
// set the max value to be < 100 - but effectively, clip larger values to 99.99
85+
// here we use the .withMax() method that takes 4 params vs. calling 4 .withMax*() methods.
86+
.withMax(new BigDecimal("100.00"), false, CLIP, new BigDecimal("0.01"))
87+
);
88+
----
89+
5990
===== DynamicDefaultValueBehavior
6091
Used to set a dynamic default value to a field when it is being inserted or updated.
6192
For example, instead of having a hard-coded `defaultValue` specified in the field meta-data,

docs/metaData/MetaDataProduction.adoc

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -288,33 +288,49 @@ all of the fields in you table in two places (the entity and the table meta-data
288288
If you are using `QRecordEntity` classes that correspond to your tables, then you can take advantage of some
289289
additional annotations on those classes, to produce more related meta-data objects associated with those tables.
290290
The point of this is to eliminate boilerplate, and simplify / speed up the process of getting a new table
291-
built and deployed in your application, with some bells and whistles added.
291+
built and deployed in your application.
292+
293+
Furthermore, the case can be made that it is beneficial to keep the meta-data definition for a table as close
294+
as possible to the entity that corresponds to the table. This enables modifications to the table (e.g., adding
295+
a new field/column) to only require edits in one java source file, rather than necessarily requiring edits
296+
in two files.
292297

293298
=== @QMetaDataProducingEntity
294-
This is an annotation to go on a QRecordEntity class, which you would like to be
295-
processed by `MetaDataProducerHelper`, to automatically produce some meta-data
296-
objects. Specifically supports:
299+
This is an annotation meant to be placed on a `QRecordEntity` subclass, which you would like to be
300+
processed by an invocation of `MetaDataProducerHelper`, to automatically produce some meta-data
301+
objects.
302+
303+
This annotation supports:
297304

298-
* Making a possible-value-source out of the table.
305+
* Creating table meta-data for the corresponding record entity table. Enabled by setting `produceTableMetaData=true`.
306+
** One may customize the table meta data that is produced automatically by supplying a class that extends
307+
`MetaDataCustomizerInterface` in the annotation attribute `tableMetaDataCustomizer`.
308+
** In addition to (or as an alternative to) the per-table `MetaDataCustomizerInterface` that can be specified
309+
in `@QMetaDataProducingEntity.tableMetaDataCustomzier`, when an application calls
310+
`MetaDataProducerHelper.processAllMetaDataProducersInPackage`, an additional `MetaDataCustomizerInterface` can be
311+
given, to apply a common set of adjustments to all tales being generated by the call.
312+
* Making a possible-value-source out of the table. Enabled by setting `producePossibleValueSource=true`.
299313
* Processing child tables to create joins and childRecordList widgets
300314

301315
=== @ChildTable
302316
This is an annotation used as a value that goes inside a `@QMetadataProducingEntity` annotation, to define
303317
child-tables, e.g., for producing joins and childRecordList widgets related to the table defined in the entity class.
304318

305-
=== @ChildJoin
319+
==== @ChildJoin
306320
This is an annotation used as a value inside a `@ChildTable` inside a `@QMetadataProducingEntity` annotation,
307321
to control the generation of a `QJoinMetaData`, as a `ONE_TO_MANY` type join from the table represented by
308322
the annotated entity, to the table referenced in the `@ChildTable` annotation.
309323

310-
=== @ChildRecordListWidget
324+
==== @ChildRecordListWidget
311325
This is an annotation used as a value that goes inside a `@QMetadataProducingEntity` annotation, to control
312326
the generation of a QWidgetMetaData - for a ChildRecordList widget.
313327

314328
[source,java]
315-
.QRecordEntity with meta-data producing annotations
329+
.QRecordEntity with meta-data producing annotations and a table MetaDataCustomizer
316330
----
317331
@QMetaDataProducingEntity(
332+
produceTableMetaData = true,
333+
tableMetaDataCustomizer = MyTable.TableMetaDataCustomizer.class,
318334
producePossibleValueSource = true,
319335
childTables = {
320336
@ChildTable(
@@ -326,13 +342,47 @@ the generation of a QWidgetMetaData - for a ChildRecordList widget.
326342
public class MyTable extends QRecordEntity
327343
{
328344
public static final String TABLE_NAME = "myTable";
329-
// class body left as exercise for reader
345+
346+
public static class TableMetaDataCustomizer implements MetaDataCustomizerInterface<QTableMetaData>
347+
{
348+
@Override
349+
public QTableMetaData customizeMetaData(QInstance qInstance, QTableMetaData table) throws QException
350+
{
351+
String childJoinName = QJoinMetaData.makeInferredJoinName(TABLE_NAME, MyChildTable.TABLE_NAME);
352+
353+
table
354+
.withUniqueKey(new UniqueKey("name"))
355+
.withIcon(new QIcon().withName("table_bar"))
356+
.withRecordLabelFormat("%s")
357+
.withRecordLabelFields("name")
358+
359+
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1,
360+
List.of("id", "name")))
361+
// todo additional sections for other fields
362+
.withSection(new QFieldSection("children", new QIcon().withName("account_tree"), Tier.T2)
363+
.withWidgetName(childJoinName))
364+
365+
.withExposedJoin(new ExposedJoin()
366+
.withLabel("Children")
367+
.withJoinPath(List.of(childJoinName))
368+
.withJoinTable(MyChildTable.TABLE_NAME));
369+
370+
return (table);
371+
}
372+
}
373+
374+
@QField(isEditable = false, isPrimaryKey = true)
375+
private Integer id;
376+
377+
// remaining fields, constructors, getters & setters left as an exercise for the reader and/or the IDE
330378
}
331379
----
332380

333381
The class given in the example above, if processed by the `MetaDataProducerHelper`, would add the following
334382
meta-data objects to your `QInstance`:
335383

384+
* A `QTableMetaData` named `myTable`, with all fields annotated as `@QField` from the `QRecordEntity` class,
385+
and with additional attributes as set in the `TableMetaDataCustomizer` inner class.
336386
* A `QPossibleValueSource` named `myTable`, of type `TABLE`, with `myTable` as its backing table.
337387
* A `QJoinMetaData` named `myTableJoinMyChildTable`, as a `ONE_TO_MANY` type, between those two tables.
338388
* A `QWidgetMetaData` named `myTableJoinMyChildTable`, as a `CHILD_RECORD_LIST` type, that will show a list of

0 commit comments

Comments
 (0)