diff --git a/.github/workflows/example-gate.yml b/.github/workflows/example-gate.yml index da97f1ba..37499131 100644 --- a/.github/workflows/example-gate.yml +++ b/.github/workflows/example-gate.yml @@ -9,6 +9,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Install example verification tools + run: sudo apt-get update && sudo apt-get install -y ripgrep - uses: actions/setup-java@v4 with: distribution: temurin diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dcdada1..112c6149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.555-RELEASE] - 2026-10-13 + +Tracking: [Java #209](https://github.com/teaql/teaql-java/issues/209) and +[conformance #109](https://github.com/teaql/teaql-conformance/issues/109). + +- Add the optional `teaql-opentelemetry` standard module with trusted environment + activation, startup guidance and full safe traces, metrics and logs. +- Preserve expanded masked SQL, carried physical execution identity, request-tree + comments and independent graph mutation audit. No per-method SDK setup is needed. +- Bound queued/exporting work, acknowledge receiver rejection separately from + failed batches, and preserve business outcomes during finite shutdown/export failure. +- Retire the separate page terminal; regenerate libraries and use ordinary list + execution with offset/limit and explicit count where needed. + +Source and internal-candidate evidence is retained in the conformance +`2026-10-13-standard-telemetry-onboarding` report. Signed-tag and final public +artifact verification remain separate release gates. + ## [1.526-RELEASE] - 2025-07-25 ### Added diff --git a/examples/business-id-runtime/pom.xml b/examples/business-id-runtime/pom.xml index 59b8df27..a2196101 100644 --- a/examples/business-id-runtime/pom.xml +++ b/examples/business-id-runtime/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../../pom.xml teaql-business-id-example diff --git a/examples/conformance/lib/pom.xml b/examples/conformance/lib/pom.xml index 134ccf0b..7e2df1b4 100644 --- a/examples/conformance/lib/pom.xml +++ b/examples/conformance/lib/pom.xml @@ -12,7 +12,7 @@ 21 4.1.1 - 1.554-RELEASE + 1.555-RELEASE UTF-8 true diff --git a/examples/conformance/pom.xml b/examples/conformance/pom.xml index 71ab3830..492d0b69 100644 --- a/examples/conformance/pom.xml +++ b/examples/conformance/pom.xml @@ -16,7 +16,7 @@ 1.0.0 UTF-8 true - 1.554-RELEASE + 1.555-RELEASE diff --git a/examples/order-management/java-app-console/pom.xml b/examples/order-management/java-app-console/pom.xml index d78ee861..05ec378c 100644 --- a/examples/order-management/java-app-console/pom.xml +++ b/examples/order-management/java-app-console/pom.xml @@ -11,7 +11,7 @@ 21 UTF-8 - 1.554-RELEASE + 1.555-RELEASE diff --git a/examples/order-management/java-lib-core/lib/pom.xml b/examples/order-management/java-lib-core/lib/pom.xml index 7d83f2d3..b8fe9739 100644 --- a/examples/order-management/java-lib-core/lib/pom.xml +++ b/examples/order-management/java-lib-core/lib/pom.xml @@ -12,7 +12,7 @@ 21 4.1.1 - 1.554-RELEASE + 1.555-RELEASE UTF-8 true diff --git a/examples/round-trip-reference-runtime/pom.xml b/examples/round-trip-reference-runtime/pom.xml index f8746316..635f65c9 100644 --- a/examples/round-trip-reference-runtime/pom.xml +++ b/examples/round-trip-reference-runtime/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../../pom.xml teaql-round-trip-reference-example diff --git a/examples/school-management/lib/pom.xml b/examples/school-management/lib/pom.xml index 721bdeda..b59d0e19 100644 --- a/examples/school-management/lib/pom.xml +++ b/examples/school-management/lib/pom.xml @@ -12,7 +12,7 @@ 21 4.1.1 - 1.554-RELEASE + 1.555-RELEASE UTF-8 true diff --git a/examples/school-management/pom.xml b/examples/school-management/pom.xml index 4af77b4f..df27e567 100644 --- a/examples/school-management/pom.xml +++ b/examples/school-management/pom.xml @@ -16,7 +16,7 @@ 0.0.1-SNAPSHOT UTF-8 true - 1.554-RELEASE + 1.555-RELEASE diff --git a/examples/school-management/src/main/java/com/example/schoolmanagementservice/RequestIntentVerifier.java b/examples/school-management/src/main/java/com/example/schoolmanagementservice/RequestIntentVerifier.java index 4fdfece6..402fb890 100644 --- a/examples/school-management/src/main/java/com/example/schoolmanagementservice/RequestIntentVerifier.java +++ b/examples/school-management/src/main/java/com/example/schoolmanagementservice/RequestIntentVerifier.java @@ -41,7 +41,7 @@ static void verify(TeaQLRuntime original) { required("REQUEST_COMMENT_REQUIRED", () -> runtime.executeForList(context, Q.schools())); required("REQUEST_COMMENT_REQUIRED", () -> runtime.executeForStream(context, Q.schools())); required("REQUEST_COMMENT_REQUIRED", () -> runtime.aggregation(context, Q.schools())); - required("REQUEST_COMMENT_REQUIRED", () -> runtime.executeForPage(context, Q.schools(), 0, 10)); + required("REQUEST_COMMENT_REQUIRED", () -> runtime.executeForList(context, Q.schools().offset(0, 10).count())); required("QUERY_PURPOSE_REQUIRED", () -> runtime.executeForList(context, Q.schools().comment("load schools"))); require(queryPolicyCalls.get() == 0 && statements.isEmpty(), "Invalid query reached policy or SQL"); for (String blank : new String[]{"", " \t\r\n", "\u2003", "\u00a0"}) { diff --git a/examples/security-foundations/pom.xml b/examples/security-foundations/pom.xml index d9c1d45d..76356218 100644 --- a/examples/security-foundations/pom.xml +++ b/examples/security-foundations/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../../pom.xml teaql-security-foundations-example diff --git a/examples/shared-load-state/pom.xml b/examples/shared-load-state/pom.xml index dfafaa26..4044e81d 100644 --- a/examples/shared-load-state/pom.xml +++ b/examples/shared-load-state/pom.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - io.teaqlteaql-java-parent1.554-RELEASE + io.teaqlteaql-java-parent1.555-RELEASE ../../pom.xml shared-load-state-example diff --git a/examples/shared-load-state/src/test/java/com/example/schoolmanagementservice/GeneratedPageStreamAcceptance.java b/examples/shared-load-state/src/test/java/com/example/schoolmanagementservice/GeneratedPageStreamAcceptance.java index 2bdcb55f..2936f57c 100644 --- a/examples/shared-load-state/src/test/java/com/example/schoolmanagementservice/GeneratedPageStreamAcceptance.java +++ b/examples/shared-load-state/src/test/java/com/example/schoolmanagementservice/GeneratedPageStreamAcceptance.java @@ -9,11 +9,13 @@ final class GeneratedPageStreamAcceptance { static void verify(UserContext context, String first, String second) { var left = Q.schoolsWithMinimalFields().withNameIn(first,second).selectName().orderByIdAscending() + .offset(0,1).count() .comment("what: load the first matching School page").purpose("why: qualify sparse page hydration") - .executeForPage(context,0,1); + .executeForList(context); var right = Q.schoolsWithMinimalFields().withNameIn(first,second).selectName().orderByIdAscending() + .offset(1,1).count() .comment("what: load the second matching School page").purpose("why: qualify adjacent pages and filtered total") - .executeForPage(context,1,1); + .executeForList(context); assertEquals(2,left.getTotalCount());assertEquals(2,right.getTotalCount()); assertEquals(1,left.size());assertEquals(1,right.size()); assertNotEquals(left.first().getId(),right.first().getId()); diff --git a/examples/standard-order/AGENTS.md b/examples/standard-order/AGENTS.md new file mode 100644 index 00000000..3ff1537c --- /dev/null +++ b/examples/standard-order/AGENTS.md @@ -0,0 +1,27 @@ +# Java Standard Order Example Rules + +Implement only application-owned code. The files under `generated/lib` are +read-only. Discover generated APIs using progressive model-aware Assist with +`models/model.xml`; never read generated source to discover methods. + +```bash +cargo teaql --input models/model.xml java-assist-query/customer_order +cargo teaql --input models/model.xml java-assist-query/customer_order.order_item_list +``` + +Use bounded Q queries through one trusted UserContext, with non-empty comment +and purpose. Configure offset/limit on the request and execute through +`executeForList(context)`. Do not introduce a separate `executeForPage` entry. +Requested count, facet and relationship metadata belong to the returned +SmartList. Use generated E for loaded-value traversal and preserve NotLoaded +as an error. + +Use generated audited mutation APIs for seed data; do not handwrite INSERTs. +Runtime, providers and policy are assembled once. Standard telemetry is selected +by trusted environment settings; business methods contain no SDK initialization +or manually created spans. Ordinary SQL output and telemetry are independent. + +Verify against repository-local Maven reactor modules. Run `bash verify.sh` and +retain startup messages, results and generated-library hashes. Backend delivery +is verified separately by conformance's controlled capture. Do not claim export +success merely because the application exits successfully. diff --git a/examples/standard-order/README.md b/examples/standard-order/README.md new file mode 100644 index 00000000..57bb9e86 --- /dev/null +++ b/examples/standard-order/README.md @@ -0,0 +1,43 @@ +# Java Standard Telemetry Order Example + +This example uses generated Q/E and the ordinary `executeForList(context)` +entry point to load a filtered order list with bounded pagination, status +details, child previews, child counts and status facets. No business method +initializes an SDK or creates a span. + +The five-object model seeds one Platform and three status constants through +generated audited mutations. Repeated ensureSchema does not produce duplicate +writes or audits. Four orders and twenty items are created through the same +mutation API. The filtered result contains three orders; the selected one-row +slice has three item previews and an independent count of five items. Status +facets have counts `1, 2, 0` and amount totals `100, 500, 0`. The deliberately +omitted order amount remains NotLoaded. + +The local runtime handles a declared ungrouped count alongside rows under one +logical list invocation. Grouped and mixed aggregate requests keep their +existing aggregate semantics. All generated library files are produced from +the checked model; do not patch them to change query behavior. + +Run the local application acceptance twice, with startup guidance and unchanged +generated-library hashes: + +```bash +bash examples/standard-order/verify.sh +``` + +The optional standard module enables all supported signals with these trusted +process settings: + +```bash +TEAQL_TELEMETRY_ENABLED=true \ +TEAQL_TELEMETRY_SERVICE_NAME=order-example \ +TEAQL_TELEMETRY_OTLP_ENDPOINT=http://127.0.0.1:4318 \ + mvn -q -Pruntime-examples -pl examples/standard-order -am \ + -Dtest=StandardOrderTelemetryTest -Dsurefire.failIfNoSpecifiedTests=false test +``` + +Ordinary SQL logs are disabled in the example. The checked model marks order +numbers and customer names for masking; telemetry must still export safe +expanded SQL. Conformance's `telemetry/capture-java-standard-order.cjs` checks +actual backend delivery and parentage. A successful application run alone does +not establish that all SQL diagnostics reached the collector. diff --git a/examples/standard-order/generated/lib/pom.xml b/examples/standard-order/generated/lib/pom.xml new file mode 100644 index 00000000..3696cf80 --- /dev/null +++ b/examples/standard-order/generated/lib/pom.xml @@ -0,0 +1,142 @@ + + + 4.0.0 + + com.example + request-tree-order-service-core + 0.0.1-SNAPSHOT + jar + + + 21 + 4.1.1 + 1.555-RELEASE + UTF-8 + true + + + + + teaql + https://maven.teaql.io/repository/maven-releases/ + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + org.apache.tomcat.embed + tomcat-embed-core + 11.0.26 + + + org.apache.tomcat.embed + tomcat-embed-el + 11.0.26 + + + org.apache.tomcat.embed + tomcat-embed-websocket + 11.0.26 + + + + + + + org.springframework.boot + spring-boot-autoconfigure + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework + spring-tx + + + io.teaql + teaql-sqlite + 1.555-RELEASE + + + io.teaql + teaql-core + ${teaql.version} + + + io.teaql + teaql-runtime + ${teaql.version} + + + cn.hutool + hutool-all + 5.8.20 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.12.1 + + ${java.version} + ${maven.compiler.parameters} + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.5.0 + + + enforce-java-version + + enforce + + + + + [${java.version},) + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + attach-sources + + jar-no-fork + + + + + + + + + + target-repo + https://maven.teaql.io/repository/maven-releases/ + + + diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/BaseServiceConfiguration.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/BaseServiceConfiguration.java new file mode 100644 index 00000000..e69de29b diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/Checkers.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/Checkers.java new file mode 100644 index 00000000..e69de29b diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/Constants.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/Constants.java new file mode 100644 index 00000000..bdd12b2e --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/Constants.java @@ -0,0 +1,14 @@ + +package com.example.requesttreeorderservice; + +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import com.example.requesttreeorderservice.platform.Platform; + +public interface Constants { + public static final long PLATFORM_ID = 1l; + public static final Platform PLATFORM = Platform.refer(PLATFORM_ID); + public static final long ORDER_STATUS_NEW_ID = 1001l ; + public static final OrderStatus ORDER_STATUS_NEW = OrderStatus.refer(ORDER_STATUS_NEW_ID);public static final long ORDER_STATUS_PAID_ID = 1002l ; + public static final OrderStatus ORDER_STATUS_PAID = OrderStatus.refer(ORDER_STATUS_PAID_ID);public static final long ORDER_STATUS_CANCELLED_ID = 1003l ; + public static final OrderStatus ORDER_STATUS_CANCELLED = OrderStatus.refer(ORDER_STATUS_CANCELLED_ID); +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/E.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/E.java new file mode 100644 index 00000000..84a3bdae --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/E.java @@ -0,0 +1,32 @@ + +package com.example.requesttreeorderservice; + +import com.example.requesttreeorderservice.customer.Customer; +import com.example.requesttreeorderservice.customer.CustomerExpression; +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.customerorder.CustomerOrderExpression; +import com.example.requesttreeorderservice.orderitem.OrderItem; +import com.example.requesttreeorderservice.orderitem.OrderItemExpression; +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import com.example.requesttreeorderservice.orderstatus.OrderStatusExpression; +import com.example.requesttreeorderservice.platform.Platform; +import com.example.requesttreeorderservice.platform.PlatformExpression; +import io.teaql.core.value.ValueExpression; + +public class E { + public static PlatformExpression platform(Platform platform){ + return new PlatformExpression(new ValueExpression(platform)); + } + public static OrderStatusExpression orderStatus(OrderStatus orderStatus){ + return new OrderStatusExpression(new ValueExpression(orderStatus)); + } + public static CustomerExpression customer(Customer customer){ + return new CustomerExpression(new ValueExpression(customer)); + } + public static CustomerOrderExpression customerOrder(CustomerOrder customerOrder){ + return new CustomerOrderExpression(new ValueExpression(customerOrder)); + } + public static OrderItemExpression orderItem(OrderItem orderItem){ + return new OrderItemExpression(new ValueExpression(orderItem)); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/EntityMetaRegistry.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/EntityMetaRegistry.java new file mode 100644 index 00000000..2d33ef16 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/EntityMetaRegistry.java @@ -0,0 +1,558 @@ + +package com.example.requesttreeorderservice; + +import io.teaql.core.FieldLayout; +import io.teaql.core.meta.EntityDescriptor; +import io.teaql.core.meta.EntityMetaAssembler; +import io.teaql.core.meta.EntityMetaFactory; +import io.teaql.core.meta.PropertyDescriptor; +import java.math.BigDecimal; +import java.time.LocalDate; + +public class EntityMetaRegistry implements EntityMetaAssembler { + private EntityMetaFactory $factory; + + @Override + public void assemble(EntityMetaFactory factory) { + this.$factory = factory; + registerPlatform(); + registerOrderStatus(); + registerCustomer(); + registerCustomerOrder(); + registerOrderItem(); + } + private void registerPlatform() { + FieldLayout.installGenerated(com.example.requesttreeorderservice.platform.Platform.__TEAQL_FIELD_LAYOUT); + EntityDescriptor entityDescriptor = new EntityDescriptor(); + entityDescriptor.setType(com.example.requesttreeorderservice.platform.Platform.INTERNAL_TYPE); + entityDescriptor.setTargetType(com.example.requesttreeorderservice.platform.Platform.class); + entityDescriptor.setEntitySupplier(com.example.requesttreeorderservice.platform.Platform::new); + entityDescriptor.with("name", "Commerce Platform") + .with("module", "Commerce") + .with("module_key", "commerce"); + + entityDescriptor.setAuditMaskFields(java.util.List.of()); + PropertyDescriptor id = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.platform.Platform.ID_PROPERTY, Long.class) + ; + PropertyDescriptor name = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.platform.Platform.NAME_PROPERTY, String.class) + ; + PropertyDescriptor version = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.platform.Platform.VERSION_PROPERTY, Long.class) + ; + entityDescriptor.findProperty(com.example.requesttreeorderservice.platform.Platform.ID_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.platform.Platform.ID_PROPERTY).with("isPassword", "false") + .with("isVersion", "false") + .with("oracle_sqlType", "number(11)") + .with("javaType", "java.lang.Long") + .with("sqlType", "BIGINT") + .with("isId", "true") + .with("isBaseEntityField", "true") + .with("isBool", "false") + .with("isNumber", "false") + .with("isString", "false") + .with("isDate", "false") + .with("snowflake_sqlType", "number") + .with("graphqlType", "Long") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.platform.Platform.NAME_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.platform.Platform.NAME_PROPERTY).with("isPassword", "false") + .with("max", "100") + .with("isVersion", "false") + .with("javaType", "java.lang.String") + .with("candidates", "Commerce Test Platform") + .with("sqlType", "VARCHAR()") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "false") + .with("isString", "true") + .with("isDate", "false") + .with("graphqlType", "String") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.platform.Platform.VERSION_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.platform.Platform.VERSION_PROPERTY).with("isPassword", "false") + .with("isVersion", "true") + .with("oracle_sqlType", "number(11)") + .with("javaType", "java.lang.Long") + .with("sqlType", "BIGINT") + .with("isId", "false") + .with("isBaseEntityField", "true") + .with("isBool", "false") + .with("isNumber", "false") + .with("isString", "false") + .with("isDate", "false") + .with("snowflake_sqlType", "number") + .with("graphqlType", "Long") + .with("isTime", "false") + .with("isText", "false"); + + $factory.register(entityDescriptor); + } + private void registerOrderStatus() { + FieldLayout.installGenerated(com.example.requesttreeorderservice.orderstatus.OrderStatus.__TEAQL_FIELD_LAYOUT); + EntityDescriptor entityDescriptor = new EntityDescriptor(); + entityDescriptor.setType(com.example.requesttreeorderservice.orderstatus.OrderStatus.INTERNAL_TYPE); + entityDescriptor.setTargetType(com.example.requesttreeorderservice.orderstatus.OrderStatus.class); + entityDescriptor.setEntitySupplier(com.example.requesttreeorderservice.orderstatus.OrderStatus::new); + entityDescriptor.with("name", "Order Status") + .with("module", "Commerce") + .with("module_key", "commerce") + .with("constant", "true") + .with("identifier", "code"); + + entityDescriptor.setAuditMaskFields(java.util.List.of()); + PropertyDescriptor platform = + entityDescriptor.addObjectProperty($factory, com.example.requesttreeorderservice.orderstatus.OrderStatus.PLATFORM_PROPERTY, com.example.requesttreeorderservice.platform.Platform.INTERNAL_TYPE, com.example.requesttreeorderservice.platform.Platform.ORDER_STATUS_LIST_PROPERTY, com.example.requesttreeorderservice.platform.Platform.class) + ; + PropertyDescriptor id = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.ID_PROPERTY, Long.class) + ; + PropertyDescriptor name = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.NAME_PROPERTY, String.class) + ; + PropertyDescriptor code = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.CODE_PROPERTY, String.class) + ; + PropertyDescriptor color = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.COLOR_PROPERTY, String.class) + ; + PropertyDescriptor displayOrder = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.DISPLAY_ORDER_PROPERTY, BigDecimal.class) + ; + PropertyDescriptor version = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.VERSION_PROPERTY, Long.class) + ; + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.PLATFORM_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.PLATFORM_PROPERTY).with("candidates", "platform()"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.ID_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.ID_PROPERTY).with("isPassword", "false") + .with("isVersion", "false") + .with("oracle_sqlType", "number(11)") + .with("javaType", "java.lang.Long") + .with("candidates", "1001,1002,1003") + .with("sqlType", "BIGINT") + .with("isId", "true") + .with("isBaseEntityField", "true") + .with("isBool", "false") + .with("isNumber", "false") + .with("isString", "false") + .with("isDate", "false") + .with("snowflake_sqlType", "number") + .with("graphqlType", "Long") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.NAME_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.NAME_PROPERTY).with("isPassword", "false") + .with("max", "100") + .with("isVersion", "false") + .with("javaType", "java.lang.String") + .with("candidates", "New,Paid,Cancelled") + .with("sqlType", "VARCHAR()") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "false") + .with("isString", "true") + .with("isDate", "false") + .with("graphqlType", "String") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.CODE_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.CODE_PROPERTY).with("identifier", "true") + .with("isPassword", "false") + .with("max", "100") + .with("isVersion", "false") + .with("javaType", "java.lang.String") + .with("candidates", "NEW,PAID,CANCELLED") + .with("sqlType", "VARCHAR()") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "false") + .with("isString", "true") + .with("isDate", "false") + .with("graphqlType", "String") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.COLOR_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.COLOR_PROPERTY).with("isPassword", "false") + .with("max", "100") + .with("isVersion", "false") + .with("javaType", "java.lang.String") + .with("candidates", "#94A3B8,#22C55E,#EF4444") + .with("sqlType", "VARCHAR()") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "false") + .with("isString", "true") + .with("isDate", "false") + .with("graphqlType", "String") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.DISPLAY_ORDER_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.DISPLAY_ORDER_PROPERTY).with("isPassword", "false") + .with("db2_sqlType", "decimal(19,7)") + .with("isVersion", "false") + .with("oracle_sqlType", "number(19,7)") + .with("javaType", "java.math.BigDecimal") + .with("candidates", "1,2,3") + .with("sqlType", "NUMERIC(19,7)") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "true") + .with("isString", "false") + .with("isDate", "false") + .with("graphqlType", "BigDecimal") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.VERSION_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderstatus.OrderStatus.VERSION_PROPERTY).with("isPassword", "false") + .with("isVersion", "true") + .with("oracle_sqlType", "number(11)") + .with("javaType", "java.lang.Long") + .with("sqlType", "BIGINT") + .with("isId", "false") + .with("isBaseEntityField", "true") + .with("isBool", "false") + .with("isNumber", "false") + .with("isString", "false") + .with("isDate", "false") + .with("snowflake_sqlType", "number") + .with("graphqlType", "Long") + .with("isTime", "false") + .with("isText", "false"); + + $factory.register(entityDescriptor); + } + private void registerCustomer() { + FieldLayout.installGenerated(com.example.requesttreeorderservice.customer.Customer.__TEAQL_FIELD_LAYOUT); + EntityDescriptor entityDescriptor = new EntityDescriptor(); + entityDescriptor.setType(com.example.requesttreeorderservice.customer.Customer.INTERNAL_TYPE); + entityDescriptor.setTargetType(com.example.requesttreeorderservice.customer.Customer.class); + entityDescriptor.setEntitySupplier(com.example.requesttreeorderservice.customer.Customer::new); + entityDescriptor.with("name", "Customer") + .with("module", "Commerce") + .with("module_key", "commerce") + .with("audit_mask_fields", "name"); + + entityDescriptor.setAuditMaskFields(java.util.List.of(com.example.requesttreeorderservice.customer.Customer.NAME_PROPERTY)); + PropertyDescriptor id = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.customer.Customer.ID_PROPERTY, Long.class) + ; + PropertyDescriptor platform = + entityDescriptor.addObjectProperty($factory, com.example.requesttreeorderservice.customer.Customer.PLATFORM_PROPERTY, com.example.requesttreeorderservice.platform.Platform.INTERNAL_TYPE, com.example.requesttreeorderservice.platform.Platform.CUSTOMER_LIST_PROPERTY, com.example.requesttreeorderservice.platform.Platform.class) + ; + PropertyDescriptor name = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.customer.Customer.NAME_PROPERTY, String.class) + ; + PropertyDescriptor version = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.customer.Customer.VERSION_PROPERTY, Long.class) + ; + entityDescriptor.findProperty(com.example.requesttreeorderservice.customer.Customer.ID_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.customer.Customer.ID_PROPERTY).with("isPassword", "false") + .with("isVersion", "false") + .with("oracle_sqlType", "number(11)") + .with("javaType", "java.lang.Long") + .with("sqlType", "BIGINT") + .with("isId", "true") + .with("isBaseEntityField", "true") + .with("isBool", "false") + .with("isNumber", "false") + .with("isString", "false") + .with("isDate", "false") + .with("snowflake_sqlType", "number") + .with("graphqlType", "Long") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.customer.Customer.PLATFORM_PROPERTY).with("required", "true"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.customer.Customer.NAME_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.customer.Customer.NAME_PROPERTY).with("isPassword", "false") + .with("max", "100") + .with("isVersion", "false") + .with("javaType", "java.lang.String") + .with("sqlType", "VARCHAR()") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "false") + .with("isString", "true") + .with("isDate", "false") + .with("graphqlType", "String") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.customer.Customer.VERSION_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.customer.Customer.VERSION_PROPERTY).with("isPassword", "false") + .with("isVersion", "true") + .with("oracle_sqlType", "number(11)") + .with("javaType", "java.lang.Long") + .with("sqlType", "BIGINT") + .with("isId", "false") + .with("isBaseEntityField", "true") + .with("isBool", "false") + .with("isNumber", "false") + .with("isString", "false") + .with("isDate", "false") + .with("snowflake_sqlType", "number") + .with("graphqlType", "Long") + .with("isTime", "false") + .with("isText", "false"); + + $factory.register(entityDescriptor); + } + private void registerCustomerOrder() { + FieldLayout.installGenerated(com.example.requesttreeorderservice.customerorder.CustomerOrder.__TEAQL_FIELD_LAYOUT); + EntityDescriptor entityDescriptor = new EntityDescriptor(); + entityDescriptor.setType(com.example.requesttreeorderservice.customerorder.CustomerOrder.INTERNAL_TYPE); + entityDescriptor.setTargetType(com.example.requesttreeorderservice.customerorder.CustomerOrder.class); + entityDescriptor.setEntitySupplier(com.example.requesttreeorderservice.customerorder.CustomerOrder::new); + entityDescriptor.with("name", "Customer Order") + .with("module", "Commerce") + .with("module_key", "commerce") + .with("audit_mask_fields", "order_number"); + + entityDescriptor.setAuditMaskFields(java.util.List.of(com.example.requesttreeorderservice.customerorder.CustomerOrder.ORDER_NUMBER_PROPERTY)); + PropertyDescriptor id = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.ID_PROPERTY, Long.class) + ; + PropertyDescriptor customer = + entityDescriptor.addObjectProperty($factory, com.example.requesttreeorderservice.customerorder.CustomerOrder.CUSTOMER_PROPERTY, com.example.requesttreeorderservice.customer.Customer.INTERNAL_TYPE, com.example.requesttreeorderservice.customer.Customer.CUSTOMER_ORDER_LIST_PROPERTY, com.example.requesttreeorderservice.customer.Customer.class) + ; + PropertyDescriptor status = + entityDescriptor.addObjectProperty($factory, com.example.requesttreeorderservice.customerorder.CustomerOrder.STATUS_PROPERTY, com.example.requesttreeorderservice.orderstatus.OrderStatus.INTERNAL_TYPE, com.example.requesttreeorderservice.orderstatus.OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY, com.example.requesttreeorderservice.orderstatus.OrderStatus.class) + ; + PropertyDescriptor orderNumber = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.ORDER_NUMBER_PROPERTY, String.class) + ; + PropertyDescriptor orderDate = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.ORDER_DATE_PROPERTY, LocalDate.class) + ; + PropertyDescriptor totalAmount = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.TOTAL_AMOUNT_PROPERTY, BigDecimal.class) + ; + PropertyDescriptor version = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.VERSION_PROPERTY, Long.class) + ; + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.ID_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.ID_PROPERTY).with("isPassword", "false") + .with("isVersion", "false") + .with("oracle_sqlType", "number(11)") + .with("javaType", "java.lang.Long") + .with("sqlType", "BIGINT") + .with("isId", "true") + .with("isBaseEntityField", "true") + .with("isBool", "false") + .with("isNumber", "false") + .with("isString", "false") + .with("isDate", "false") + .with("snowflake_sqlType", "number") + .with("graphqlType", "Long") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.CUSTOMER_PROPERTY).with("required", "true"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.STATUS_PROPERTY).with("required", "true"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.ORDER_NUMBER_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.ORDER_NUMBER_PROPERTY).with("isPassword", "false") + .with("max", "100") + .with("isVersion", "false") + .with("javaType", "java.lang.String") + .with("sqlType", "VARCHAR()") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "false") + .with("isString", "true") + .with("isDate", "false") + .with("graphqlType", "String") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.ORDER_DATE_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.ORDER_DATE_PROPERTY).with("isPassword", "false") + .with("isVersion", "false") + .with("javaType", "java.time.LocalDate") + .with("sqlType", "DATE") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "false") + .with("isDate", "true") + .with("isString", "false") + .with("graphqlType", "Date") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.TOTAL_AMOUNT_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.TOTAL_AMOUNT_PROPERTY).with("isPassword", "false") + .with("db2_sqlType", "decimal(19,7)") + .with("isVersion", "false") + .with("oracle_sqlType", "number(19,7)") + .with("javaType", "java.math.BigDecimal") + .with("sqlType", "NUMERIC(19,7)") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "true") + .with("isString", "false") + .with("isDate", "false") + .with("graphqlType", "BigDecimal") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.VERSION_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.customerorder.CustomerOrder.VERSION_PROPERTY).with("isPassword", "false") + .with("isVersion", "true") + .with("oracle_sqlType", "number(11)") + .with("javaType", "java.lang.Long") + .with("sqlType", "BIGINT") + .with("isId", "false") + .with("isBaseEntityField", "true") + .with("isBool", "false") + .with("isNumber", "false") + .with("isString", "false") + .with("isDate", "false") + .with("snowflake_sqlType", "number") + .with("graphqlType", "Long") + .with("isTime", "false") + .with("isText", "false"); + + $factory.register(entityDescriptor); + } + private void registerOrderItem() { + FieldLayout.installGenerated(com.example.requesttreeorderservice.orderitem.OrderItem.__TEAQL_FIELD_LAYOUT); + EntityDescriptor entityDescriptor = new EntityDescriptor(); + entityDescriptor.setType(com.example.requesttreeorderservice.orderitem.OrderItem.INTERNAL_TYPE); + entityDescriptor.setTargetType(com.example.requesttreeorderservice.orderitem.OrderItem.class); + entityDescriptor.setEntitySupplier(com.example.requesttreeorderservice.orderitem.OrderItem::new); + entityDescriptor.with("name", "Order Item") + .with("module", "Commerce") + .with("module_key", "commerce"); + + entityDescriptor.setAuditMaskFields(java.util.List.of()); + PropertyDescriptor id = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderitem.OrderItem.ID_PROPERTY, Long.class) + ; + PropertyDescriptor customerOrder = + entityDescriptor.addObjectProperty($factory, com.example.requesttreeorderservice.orderitem.OrderItem.CUSTOMER_ORDER_PROPERTY, com.example.requesttreeorderservice.customerorder.CustomerOrder.INTERNAL_TYPE, com.example.requesttreeorderservice.customerorder.CustomerOrder.ORDER_ITEM_LIST_PROPERTY, com.example.requesttreeorderservice.customerorder.CustomerOrder.class) + ; + PropertyDescriptor productName = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderitem.OrderItem.PRODUCT_NAME_PROPERTY, String.class) + ; + PropertyDescriptor quantity = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderitem.OrderItem.QUANTITY_PROPERTY, Integer.class) + ; + PropertyDescriptor unitPrice = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderitem.OrderItem.UNIT_PRICE_PROPERTY, BigDecimal.class) + ; + PropertyDescriptor version = + entityDescriptor.addSimpleProperty(com.example.requesttreeorderservice.orderitem.OrderItem.VERSION_PROPERTY, Long.class) + ; + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.ID_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.ID_PROPERTY).with("isPassword", "false") + .with("isVersion", "false") + .with("oracle_sqlType", "number(11)") + .with("javaType", "java.lang.Long") + .with("sqlType", "BIGINT") + .with("isId", "true") + .with("isBaseEntityField", "true") + .with("isBool", "false") + .with("isNumber", "false") + .with("isString", "false") + .with("isDate", "false") + .with("snowflake_sqlType", "number") + .with("graphqlType", "Long") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.CUSTOMER_ORDER_PROPERTY).with("required", "true"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.PRODUCT_NAME_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.PRODUCT_NAME_PROPERTY).with("isPassword", "false") + .with("max", "100") + .with("isVersion", "false") + .with("javaType", "java.lang.String") + .with("sqlType", "VARCHAR()") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "false") + .with("isString", "true") + .with("isDate", "false") + .with("graphqlType", "String") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.QUANTITY_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.QUANTITY_PROPERTY).with("isPassword", "false") + .with("db2_sqlType", "decimal(19,7)") + .with("isVersion", "false") + .with("oracle_sqlType", "number(19,7)") + .with("javaType", "java.lang.Integer") + .with("sqlType", "INTEGER") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "true") + .with("isInt", "true") + .with("isString", "false") + .with("isDate", "false") + .with("graphqlType", "Int") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.UNIT_PRICE_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.UNIT_PRICE_PROPERTY).with("isPassword", "false") + .with("db2_sqlType", "decimal(19,7)") + .with("isVersion", "false") + .with("oracle_sqlType", "number(19,7)") + .with("javaType", "java.math.BigDecimal") + .with("sqlType", "NUMERIC(19,7)") + .with("isId", "false") + .with("isBool", "false") + .with("isBaseEntityField", "false") + .with("isNumber", "true") + .with("isString", "false") + .with("isDate", "false") + .with("graphqlType", "BigDecimal") + .with("isTime", "false") + .with("isText", "false"); + + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.VERSION_PROPERTY).with("required", "true"); + entityDescriptor.findProperty(com.example.requesttreeorderservice.orderitem.OrderItem.VERSION_PROPERTY).with("isPassword", "false") + .with("isVersion", "true") + .with("oracle_sqlType", "number(11)") + .with("javaType", "java.lang.Long") + .with("sqlType", "BIGINT") + .with("isId", "false") + .with("isBaseEntityField", "true") + .with("isBool", "false") + .with("isNumber", "false") + .with("isString", "false") + .with("isDate", "false") + .with("snowflake_sqlType", "number") + .with("graphqlType", "Long") + .with("isTime", "false") + .with("isText", "false"); + + $factory.register(entityDescriptor); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/GeneratedRuntimeModule.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/GeneratedRuntimeModule.java new file mode 100644 index 00000000..b1c117f0 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/GeneratedRuntimeModule.java @@ -0,0 +1,99 @@ +package com.example.requesttreeorderservice; + +/** Passive generated manifest. Database changes require context.ensureSchema(). */ +public final class GeneratedRuntimeModule { + private static final io.teaql.core.RuntimeModule MODULE = io.teaql.core.RuntimeModule.of(new EntityMetaRegistry()) + .withCheckers(new com.example.requesttreeorderservice.platform.PlatformChecker(), new com.example.requesttreeorderservice.orderstatus.OrderStatusChecker(), new com.example.requesttreeorderservice.customer.CustomerChecker(), new com.example.requesttreeorderservice.customerorder.CustomerOrderChecker(), new com.example.requesttreeorderservice.orderitem.OrderItemChecker()) + .withBootstrap(GeneratedRuntimeModule::ensureGeneratedBootstrap); + + private GeneratedRuntimeModule() {} + public static io.teaql.core.RuntimeModule module() { return MODULE; } + + /** Canonical KSML field to selected JSON wire name, consumed by HTTP/TFP adapters. */ + public static java.util.Map> wireFieldMappings() { + return java.util.Map.ofEntries( + java.util.Map.entry("Platform", java.util.Map.ofEntries(java.util.Map.entry("id", "id"), java.util.Map.entry("name", "name"), java.util.Map.entry("version", "version"))), + java.util.Map.entry("OrderStatus", java.util.Map.ofEntries(java.util.Map.entry("platform", "platform"), java.util.Map.entry("id", "id"), java.util.Map.entry("name", "name"), java.util.Map.entry("code", "code"), java.util.Map.entry("color", "color"), java.util.Map.entry("display_order", "displayOrder"), java.util.Map.entry("version", "version"))), + java.util.Map.entry("Customer", java.util.Map.ofEntries(java.util.Map.entry("id", "id"), java.util.Map.entry("platform", "platform"), java.util.Map.entry("name", "name"), java.util.Map.entry("version", "version"))), + java.util.Map.entry("CustomerOrder", java.util.Map.ofEntries(java.util.Map.entry("id", "id"), java.util.Map.entry("customer", "customer"), java.util.Map.entry("status", "status"), java.util.Map.entry("order_number", "orderNumber"), java.util.Map.entry("order_date", "orderDate"), java.util.Map.entry("total_amount", "totalAmount"), java.util.Map.entry("version", "version"))), + java.util.Map.entry("OrderItem", java.util.Map.ofEntries(java.util.Map.entry("id", "id"), java.util.Map.entry("customer_order", "customerOrder"), java.util.Map.entry("product_name", "productName"), java.util.Map.entry("quantity", "quantity"), java.util.Map.entry("unit_price", "unitPrice"), java.util.Map.entry("version", "version"))) + ); + } + + /** Accepted legacy aliases; empty until explicitly declared by the model. */ + public static java.util.Map> wireFieldAliases() { + return java.util.Map.of(); + } + + private static void ensureGeneratedBootstrap(io.teaql.core.UserContext context) { + var domainRoots = Q.platforms().withIdIs(1L).comment("what: locate generated Domain Root").purpose("why: idempotent runtime bootstrap").executeForList(context); + com.example.requesttreeorderservice.platform.Platform domainRoot; + if (domainRoots.isEmpty()) { + domainRoot = new com.example.requesttreeorderservice.platform.Platform(); + io.teaql.core.GeneratedSchemaBootstrap.initializeFixedId(context, domainRoot, 1L); + domainRoot.updateName("Commerce Test Platform"); + domainRoot.auditAs("create generated Domain Root Platform").save(context); + } else { domainRoot = domainRoots.get(0); } + context.withActiveRoot(new io.teaql.core.ContextEntityRef("Platform", domainRoot.getId())); + var constantOrderStatus1001Rows = Q.orderStatuses().withIdIs(1001L).comment("what: locate generated constant").purpose("why: idempotent runtime bootstrap").executeForList(context); + if (constantOrderStatus1001Rows.isEmpty()) { + com.example.requesttreeorderservice.orderstatus.OrderStatus constantOrderStatus1001 = new com.example.requesttreeorderservice.orderstatus.OrderStatus(); + constantOrderStatus1001 = io.teaql.core.GeneratedSchemaBootstrap.initializeFixedId(context, constantOrderStatus1001, 1001L); + constantOrderStatus1001.updatePlatform(domainRoot); + constantOrderStatus1001.updateName("New"); + constantOrderStatus1001.updateCode("NEW"); + constantOrderStatus1001.updateColor("#94A3B8"); + constantOrderStatus1001.updateDisplayOrder(new java.math.BigDecimal("1")); + constantOrderStatus1001.auditAs("create model constant OrderStatus(1001)").save(context); + } else { + com.example.requesttreeorderservice.orderstatus.OrderStatus constantOrderStatus1001 = constantOrderStatus1001Rows.get(0); + boolean changed = false; + if (!(java.util.Objects.equals(constantOrderStatus1001.getPlatform().getId(), domainRoot.getId()))) { constantOrderStatus1001.updatePlatform(domainRoot); changed = true; } + if (!(java.util.Objects.equals(constantOrderStatus1001.getName(), "New"))) { constantOrderStatus1001.updateName("New"); changed = true; } + if (!(java.util.Objects.equals(constantOrderStatus1001.getCode(), "NEW"))) { constantOrderStatus1001.updateCode("NEW"); changed = true; } + if (!(java.util.Objects.equals(constantOrderStatus1001.getColor(), "#94A3B8"))) { constantOrderStatus1001.updateColor("#94A3B8"); changed = true; } + if (!((constantOrderStatus1001.getDisplayOrder() != null && constantOrderStatus1001.getDisplayOrder().compareTo(new java.math.BigDecimal("1")) == 0))) { constantOrderStatus1001.updateDisplayOrder(new java.math.BigDecimal("1")); changed = true; } + if (changed) constantOrderStatus1001.auditAs("reconcile model constant OrderStatus(1001)").save(context); + } + var constantOrderStatus1002Rows = Q.orderStatuses().withIdIs(1002L).comment("what: locate generated constant").purpose("why: idempotent runtime bootstrap").executeForList(context); + if (constantOrderStatus1002Rows.isEmpty()) { + com.example.requesttreeorderservice.orderstatus.OrderStatus constantOrderStatus1002 = new com.example.requesttreeorderservice.orderstatus.OrderStatus(); + constantOrderStatus1002 = io.teaql.core.GeneratedSchemaBootstrap.initializeFixedId(context, constantOrderStatus1002, 1002L); + constantOrderStatus1002.updatePlatform(domainRoot); + constantOrderStatus1002.updateName("Paid"); + constantOrderStatus1002.updateCode("PAID"); + constantOrderStatus1002.updateColor("#22C55E"); + constantOrderStatus1002.updateDisplayOrder(new java.math.BigDecimal("2")); + constantOrderStatus1002.auditAs("create model constant OrderStatus(1002)").save(context); + } else { + com.example.requesttreeorderservice.orderstatus.OrderStatus constantOrderStatus1002 = constantOrderStatus1002Rows.get(0); + boolean changed = false; + if (!(java.util.Objects.equals(constantOrderStatus1002.getPlatform().getId(), domainRoot.getId()))) { constantOrderStatus1002.updatePlatform(domainRoot); changed = true; } + if (!(java.util.Objects.equals(constantOrderStatus1002.getName(), "Paid"))) { constantOrderStatus1002.updateName("Paid"); changed = true; } + if (!(java.util.Objects.equals(constantOrderStatus1002.getCode(), "PAID"))) { constantOrderStatus1002.updateCode("PAID"); changed = true; } + if (!(java.util.Objects.equals(constantOrderStatus1002.getColor(), "#22C55E"))) { constantOrderStatus1002.updateColor("#22C55E"); changed = true; } + if (!((constantOrderStatus1002.getDisplayOrder() != null && constantOrderStatus1002.getDisplayOrder().compareTo(new java.math.BigDecimal("2")) == 0))) { constantOrderStatus1002.updateDisplayOrder(new java.math.BigDecimal("2")); changed = true; } + if (changed) constantOrderStatus1002.auditAs("reconcile model constant OrderStatus(1002)").save(context); + } + var constantOrderStatus1003Rows = Q.orderStatuses().withIdIs(1003L).comment("what: locate generated constant").purpose("why: idempotent runtime bootstrap").executeForList(context); + if (constantOrderStatus1003Rows.isEmpty()) { + com.example.requesttreeorderservice.orderstatus.OrderStatus constantOrderStatus1003 = new com.example.requesttreeorderservice.orderstatus.OrderStatus(); + constantOrderStatus1003 = io.teaql.core.GeneratedSchemaBootstrap.initializeFixedId(context, constantOrderStatus1003, 1003L); + constantOrderStatus1003.updatePlatform(domainRoot); + constantOrderStatus1003.updateName("Cancelled"); + constantOrderStatus1003.updateCode("CANCELLED"); + constantOrderStatus1003.updateColor("#EF4444"); + constantOrderStatus1003.updateDisplayOrder(new java.math.BigDecimal("3")); + constantOrderStatus1003.auditAs("create model constant OrderStatus(1003)").save(context); + } else { + com.example.requesttreeorderservice.orderstatus.OrderStatus constantOrderStatus1003 = constantOrderStatus1003Rows.get(0); + boolean changed = false; + if (!(java.util.Objects.equals(constantOrderStatus1003.getPlatform().getId(), domainRoot.getId()))) { constantOrderStatus1003.updatePlatform(domainRoot); changed = true; } + if (!(java.util.Objects.equals(constantOrderStatus1003.getName(), "Cancelled"))) { constantOrderStatus1003.updateName("Cancelled"); changed = true; } + if (!(java.util.Objects.equals(constantOrderStatus1003.getCode(), "CANCELLED"))) { constantOrderStatus1003.updateCode("CANCELLED"); changed = true; } + if (!(java.util.Objects.equals(constantOrderStatus1003.getColor(), "#EF4444"))) { constantOrderStatus1003.updateColor("#EF4444"); changed = true; } + if (!((constantOrderStatus1003.getDisplayOrder() != null && constantOrderStatus1003.getDisplayOrder().compareTo(new java.math.BigDecimal("3")) == 0))) { constantOrderStatus1003.updateDisplayOrder(new java.math.BigDecimal("3")); changed = true; } + if (changed) constantOrderStatus1003.auditAs("reconcile model constant OrderStatus(1003)").save(context); + } + } +} diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/Q.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/Q.java new file mode 100644 index 00000000..d170581a --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/Q.java @@ -0,0 +1,52 @@ + +package com.example.requesttreeorderservice; + +import io.teaql.core.criteria.Operator; + +public class Q { + public static com.example.requesttreeorderservice.platform.PlatformRequest platforms(){ + return new com.example.requesttreeorderservice.platform.PlatformRequest(com.example.requesttreeorderservice.platform.Platform.class).selectSelf().withVersion(Operator.GREATER_THAN, 0l); + } + public static com.example.requesttreeorderservice.platform.PlatformRequest platformsWithMinimalFields(){ + return new com.example.requesttreeorderservice.platform.PlatformRequest(com.example.requesttreeorderservice.platform.Platform.class).withVersion(Operator.GREATER_THAN, 0l); + } + + + + public static com.example.requesttreeorderservice.orderstatus.OrderStatusRequest orderStatuses(){ + return new com.example.requesttreeorderservice.orderstatus.OrderStatusRequest(com.example.requesttreeorderservice.orderstatus.OrderStatus.class).selectSelf().withVersion(Operator.GREATER_THAN, 0l); + } + public static com.example.requesttreeorderservice.orderstatus.OrderStatusRequest orderStatusesWithMinimalFields(){ + return new com.example.requesttreeorderservice.orderstatus.OrderStatusRequest(com.example.requesttreeorderservice.orderstatus.OrderStatus.class).withVersion(Operator.GREATER_THAN, 0l); + } + + + + public static com.example.requesttreeorderservice.customer.CustomerRequest customers(){ + return new com.example.requesttreeorderservice.customer.CustomerRequest(com.example.requesttreeorderservice.customer.Customer.class).selectSelf().withVersion(Operator.GREATER_THAN, 0l); + } + public static com.example.requesttreeorderservice.customer.CustomerRequest customersWithMinimalFields(){ + return new com.example.requesttreeorderservice.customer.CustomerRequest(com.example.requesttreeorderservice.customer.Customer.class).withVersion(Operator.GREATER_THAN, 0l); + } + + + + public static com.example.requesttreeorderservice.customerorder.CustomerOrderRequest customerOrders(){ + return new com.example.requesttreeorderservice.customerorder.CustomerOrderRequest(com.example.requesttreeorderservice.customerorder.CustomerOrder.class).selectSelf().withVersion(Operator.GREATER_THAN, 0l); + } + public static com.example.requesttreeorderservice.customerorder.CustomerOrderRequest customerOrdersWithMinimalFields(){ + return new com.example.requesttreeorderservice.customerorder.CustomerOrderRequest(com.example.requesttreeorderservice.customerorder.CustomerOrder.class).withVersion(Operator.GREATER_THAN, 0l); + } + + + + public static com.example.requesttreeorderservice.orderitem.OrderItemRequest orderItems(){ + return new com.example.requesttreeorderservice.orderitem.OrderItemRequest(com.example.requesttreeorderservice.orderitem.OrderItem.class).selectSelf().withVersion(Operator.GREATER_THAN, 0l); + } + public static com.example.requesttreeorderservice.orderitem.OrderItemRequest orderItemsWithMinimalFields(){ + return new com.example.requesttreeorderservice.orderitem.OrderItemRequest(com.example.requesttreeorderservice.orderitem.OrderItem.class).withVersion(Operator.GREATER_THAN, 0l); + } + + + +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/Repositories.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/Repositories.java new file mode 100644 index 00000000..e69de29b diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/RequestTreeOrderServiceUserContext.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/RequestTreeOrderServiceUserContext.java new file mode 100644 index 00000000..673d21a5 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/RequestTreeOrderServiceUserContext.java @@ -0,0 +1,10 @@ + +package com.example.requesttreeorderservice; + +import io.teaql.core.UserContext; +public interface RequestTreeOrderServiceUserContext extends UserContext{ + + + + +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/Customer.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/Customer.java new file mode 100644 index 00000000..5019c241 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/Customer.java @@ -0,0 +1,141 @@ + +package com.example.requesttreeorderservice.customer; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.platform.Platform; +import io.teaql.core.Audited; +import io.teaql.core.BaseEntity; +import io.teaql.core.EntityStatus; +import io.teaql.core.FieldLayout; +import io.teaql.core.FrameworkInternal; +import io.teaql.core.RemoteInput; +import io.teaql.core.SmartList; +import java.util.Objects; + +/** + * [TEAQL AI WARNING] + * TeaQL was explicitly designed to PREVENT AI hallucinations and random guessing. + * DO NOT GUESS METHOD NAMES! + * The methods listed below are the ONLY valid ways to interact with this entity. + * If you encounter compilation errors (e.g., method not found), DO NOT guess another method name. + * Read the method signatures in this file before proceeding. + */ +public class Customer extends BaseEntity implements RemoteInput { + public static String INTERNAL_TYPE = "Customer"; + + // Framework metadata: complete fixed layout, independent of SQL/projection order. + public static final String __TEAQL_FIELD_LAYOUT_REVISION = "52190974dcef386c9676cd86cd564677c6cb5bb058e7e13bfea8b26db14f86a2"; + public static final java.util.Map __TEAQL_FIXED_FIELD_INDEXES = + java.util.Map.ofEntries(java.util.Map.entry("id", 0), java.util.Map.entry("platform", 1), java.util.Map.entry("name", 2), java.util.Map.entry("version", 3)); + public static final java.util.Map> __TEAQL_FIXED_FIELD_MAPPINGS = + java.util.Map.ofEntries(java.util.Map.entry("id", java.util.List.of("id", "id")), java.util.Map.entry("platform", java.util.List.of("platform", "platform")), java.util.Map.entry("name", java.util.List.of("name", "name")), java.util.Map.entry("version", java.util.List.of("version", "version"))); + public static final java.util.Set __TEAQL_FIXED_RELATION_NAMES = java.util.Set.of( + "customerOrderList"); + public static final FieldLayout __TEAQL_FIELD_LAYOUT = + FieldLayout.installGenerated(FieldLayout.generated( + Customer.class, __TEAQL_FIELD_LAYOUT_REVISION, + __TEAQL_FIXED_FIELD_INDEXES, __TEAQL_FIXED_FIELD_MAPPINGS, __TEAQL_FIXED_RELATION_NAMES)); + + + public static final String PLATFORM_PROPERTY = "platform"; + public static final String NAME_PROPERTY = "name"; + public static final String CUSTOMER_ORDER_LIST_PROPERTY = "customerOrderList"; + private Platform platform; + + private String name; + + private SmartList customerOrderList; + + public Platform getPlatform(){ + return this.platform; + } + + public String getName(){ + return this.name; + } + + public SmartList getCustomerOrderList(){ + return this.customerOrderList; + } + public Customer updatePlatform(Platform platform){ + if(Objects.equals(this.platform, platform)){ + return this; + } + handleUpdate(PLATFORM_PROPERTY, getPlatform(), platform); + this.platform = platform; + return this; + } + + public Customer updateName(String name){ + name = (name == null ? null : name.trim()); + if(Objects.equals(this.name, name)){ + return this; + } + handleUpdate(NAME_PROPERTY, getName(), name); + this.name = name; + return this; + } + + public Customer addCustomerOrder(CustomerOrder customerOrder){ + if (customerOrder == null){ + return this; + } + + if(null == this.customerOrderList){ + this.customerOrderList = new SmartList<>(); + } + + this.customerOrderList.add(customerOrder); + customerOrder.updateCustomer(this); + return this; + } + + public static Customer refer(Long id){ + Customer refer = new Customer(); + refer.__internalSet("id", id); + refer.set$status(EntityStatus.REFER); + return refer; + } + @Override + public String typeName(){ + return INTERNAL_TYPE; + } + + public Customer comment(String comment){ + this.setComment(comment); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public Audited auditAs(String action) { + return new Audited<>(this, action); + } + + // ===== Framework Internal: generated switch dispatch ===== + @Override + @FrameworkInternal + public void __internalSet(String property, Object value) { + markPropertyLoaded(property); + switch (property) { + case "platform": this.platform = (Platform) value; break; + + case "name": this.name = (value == null ? null : ((String)value).trim()); break; + + case "customerOrderList": this.customerOrderList = (SmartList) value; break; + default: super.__internalSet(property, value); + } + } + + @Override + @FrameworkInternal + public Object __internalGet(String property) { + switch (property) { + case "platform": return this.platform; + case "name": return this.name; + case "customerOrderList": return this.customerOrderList; + default: return super.__internalGet(property); + } + } + +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerChecker.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerChecker.java new file mode 100644 index 00000000..ffb923c6 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerChecker.java @@ -0,0 +1,62 @@ + +package com.example.requesttreeorderservice.customer; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.customerorder.CustomerOrderChecker; +import com.example.requesttreeorderservice.platform.Platform; +import com.example.requesttreeorderservice.platform.PlatformChecker; +import io.teaql.core.UserContext; +import io.teaql.core.checker.Checker; +import io.teaql.core.checker.ObjectLocation; + +public class CustomerChecker implements Checker{ + + public String type(){ + return Customer.INTERNAL_TYPE; + } + + public void checkAndFix(UserContext _context, Customer customer, ObjectLocation _parentLocation){ + if(needCheck(_context, customer)){ + markAsChecked(_context, customer); + doCheck(_context, customer, _parentLocation); + } + } + + public void doCheck(UserContext _context, Customer customer, ObjectLocation _parentLocation){ + if((customer == null)){ + return; + } + if(customer.newItem()){ + }else if(customer.updateItem()){ + if(!customer.isPropertyLoaded("platform")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "platform"), "Mutation requires a fully loaded entity"); + } + if(!customer.isPropertyLoaded("name")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "name"), "Mutation requires a fully loaded entity"); + } + + } + checkPlatform(_context, customer.getProperty(Customer.PLATFORM_PROPERTY), newLocation(_parentLocation, "platform")); + checkName(_context, customer.getProperty(Customer.NAME_PROPERTY), newLocation(_parentLocation, "name")); + for(int i = 0; customer.getCustomerOrderList() != null && i < customer.getCustomerOrderList().size(); i++){ + CustomerOrder customerOrder = customer.getCustomerOrderList().get(i); + new CustomerOrderChecker().checkAndFix(_context, customerOrder, newLocation(_parentLocation, "customer_order_list", i)); + } + } + + public void checkPlatform(UserContext _context, Platform platform, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, platform); + if((platform == null)){ + return; + } + new PlatformChecker().checkAndFix(_context, platform, _parentLocation); + } + public void checkName(UserContext _context, String name, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, name); + if((name == null)){ + return; + } + maxStringCheck(_context, _parentLocation, 100, name); + + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerExpression.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerExpression.java new file mode 100644 index 00000000..5cbee509 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerExpression.java @@ -0,0 +1,61 @@ + +package com.example.requesttreeorderservice.customer; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.customerorder.CustomerOrderListExpression; +import com.example.requesttreeorderservice.platform.Platform; +import com.example.requesttreeorderservice.platform.PlatformExpression; +import io.teaql.core.UserContext; +import io.teaql.core.value.BaseEntityExpression; +import io.teaql.core.value.Expression; +import io.teaql.core.value.ExpressionAdaptor; +import java.util.function.Function; + +public class CustomerExpression extends ExpressionAdaptor implements BaseEntityExpression { + public CustomerExpression(Expression expression){ + super(expression); + } + + public CustomerExpression(Expression expression, Function function){ + super(expression, function); + } + + public CustomerExpression updateId(Long id){ + return new CustomerExpression(this, $it -> {((Customer)$it).__internalSet("id", id); return this;}); + } + + public CustomerExpression save(UserContext userContext){ + return new CustomerExpression(this, $it -> ((Customer)$it).auditAs("Saved by Expression").save(userContext)); + } + + public CustomerExpression save(String intent, UserContext userContext){ + return new CustomerExpression(this, $it -> ((Customer)$it).auditAs(intent).save(userContext)); + } + + public boolean isNull() { + return resolve() == null; + } + + + public PlatformExpression getPlatform(){ + return new PlatformExpression(loaded("platform", Customer::getPlatform)); + } + + public CustomerExpression updatePlatform(Platform platform){ + return new CustomerExpression(this, $it -> ((Customer)$it).updatePlatform(platform)); + } + + public Expression getName(){ + return loaded("name", Customer::getName); + } + public CustomerExpression updateName(String name){ + return new CustomerExpression(this, $it -> ((Customer)$it).updateName(name)); + } + + public CustomerOrderListExpression getCustomerOrderList(){ + return new CustomerOrderListExpression(loaded("customerOrderList", Customer::getCustomerOrderList)); + } + public CustomerExpression addCustomerOrder(CustomerOrder customerOrder){ + return new CustomerExpression(this, $it -> ((Customer)$it).addCustomerOrder(customerOrder)); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerListExpression.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerListExpression.java new file mode 100644 index 00000000..7c856e19 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerListExpression.java @@ -0,0 +1,25 @@ + +package com.example.requesttreeorderservice.customer; + +import io.teaql.core.SmartList; +import io.teaql.core.value.Expression; +import io.teaql.core.value.SmartListExpression; +import java.util.function.Function; + +public class CustomerListExpression extends SmartListExpression { + public CustomerListExpression(Expression> expression){ + super(expression); + } + + public CustomerListExpression(Expression expression, Function> function){ + super(expression, function); + } + + public CustomerExpression first() { + return new CustomerExpression(super.first()); + } + + public CustomerExpression get(int index) { + return new CustomerExpression(super.get(index)); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerRequest.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerRequest.java new file mode 100644 index 00000000..95238396 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customer/CustomerRequest.java @@ -0,0 +1,849 @@ + +package com.example.requesttreeorderservice.customer; + +import com.example.requesttreeorderservice.Q; +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.customerorder.CustomerOrderRequest; +import com.example.requesttreeorderservice.platform.Platform; +import com.example.requesttreeorderservice.platform.PlatformRequest; +import io.teaql.core.AggrFunction; +import io.teaql.core.BaseRequest; +import io.teaql.core.PropertyReference; +import io.teaql.core.SearchCriteria; +import io.teaql.core.SubQuerySearchCriteria; +import io.teaql.core.criteria.Operator; +import io.teaql.core.criteria.TwoOperatorCriteria; + +public class CustomerRequest extends BaseRequest { + + /** + * @deprecated AI agents and business code must use the generated Q facade + * instead of constructing request builders directly. + */ + @Deprecated + @SuppressWarnings("unchecked") + public CustomerRequest(Class returnType){ + this(returnType, () -> (T) new Customer()); + } + + protected CustomerRequest(Class returnType, java.util.function.Supplier factory){ + super(returnType, factory); + selectId(); + selectVersion(); + } + + public CustomerRequest comment(String comment){ + super.internalComment(comment); + return this; + } + + // purpose() 继承自 BaseRequest,返回 ExecutableRequest(终结方法) + + public CustomerRequest returnType(Class returnType){ + super.setReturnType(returnType); + return this; + } + + public CustomerRequest enableAggregationCache(long cacheExpiredMillis){ + super.enableAggregationCache(); + super.aggregateCacheTime(cacheExpiredMillis); + return this; + } + + public CustomerRequest enableAggregationCache(){ + return enableAggregationCache(0l); + } + + + public CustomerRequest propagateAggregationCache(long cacheExpiredMillis){ + super.propagateAggregationCache(cacheExpiredMillis); + return this; + } + + /** + * Accept best-effort stateful seek optimization for browsing consecutive pages. + * Do not use this for business processing that must visit every row exactly once. + */ + public CustomerRequest optimizeForContinuousPageFetch(){ + super.optimizeForContinuousPageFetch(); + return this; + } + + public CustomerRequest optimizeForContinuousPageFetch(String namespace, int ttlSeconds){ + super.optimizeForContinuousPageFetch(namespace, ttlSeconds); + return this; + } + + public CustomerRequest optimizePaginationWithIdSet(){ + super.optimizePaginationWithIdSet(); + return this; + } + + public CustomerRequest optimizePaginationWithIdSet( + String namespace, int ttlSeconds, int maxIds){ + super.optimizePaginationWithIdSet(namespace, ttlSeconds, maxIds); + return this; + } + + public CustomerRequest topNProbeParentThreshold(int threshold){ + super.topNProbeParentThreshold(threshold); + return this; + } + + public CustomerRequest appendSearchCriteria(SearchCriteria searchCriteria){ + return (CustomerRequest)super.appendSearchCriteria(searchCriteria); + } + + public CustomerRequest filter(String property1, Operator operator, String property2){ + return appendSearchCriteria(new TwoOperatorCriteria(operator, new PropertyReference(property1), new PropertyReference(property2))); + } + + + public CustomerRequest matchingAnyOf(CustomerRequest customer){ + super.internalMatchAny(customer); + return this; + } + + public CustomerRequest enhanceChildrenIfNeeded(){ + return this; + } + + public CustomerRequest withDeletedRows(){ + super.withDeletedRows(); + return this; + } + + public CustomerRequest deletedRowsOnly(){ + super.deletedRowsOnly(); + return this; + } + + public CustomerRequest selectSelf(){ + super.selectSelf(); + return selectId().selectPlatformIdOnly().selectName().selectVersion(); + } + + public CustomerRequest selectSelfFields(){ + return selectSelf(); + } + + public CustomerRequest selectSelfWithoutParent(){ + selectId(); + selectVersion(); + selectProperty(Customer.ID_PROPERTY); + selectProperty(Customer.PLATFORM_PROPERTY); + selectProperty(Customer.NAME_PROPERTY); + selectProperty(Customer.VERSION_PROPERTY); + return this; + } + + public CustomerRequest selectAll(){ + super.selectAll(); + return selectId().selectPlatform().selectName().selectVersion(); + } + + public CustomerRequest selectChildren(){ + super.selectAny(); + selectCustomerOrderList(); + return selectId().selectPlatform().selectName().selectVersion(); + } + + + public CustomerRequest selectId(){ + selectProperty(Customer.ID_PROPERTY); + return this; + } + + /** + * fill the id with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS id) to fetch id property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public CustomerRequest unselectId(){ + unselectProperty(Customer.ID_PROPERTY); + return this; + } + public CustomerRequest selectPlatformIdOnly(){ + selectProperty(Customer.PLATFORM_PROPERTY); + return this; + } + + public CustomerRequest selectPlatform(){ + return selectPlatformWith(Q.platforms().unlimited().selectSelf()); + } + + public CustomerRequest selectPlatformWith(PlatformRequest platform){ + selectProperty(Customer.PLATFORM_PROPERTY); + enhanceRelation(Customer.PLATFORM_PROPERTY, platform, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, "selectPlatformWith", null, Customer.PLATFORM_PROPERTY)); + return this; + } + + public CustomerRequest unselectPlatform(){ + unselectProperty(Customer.PLATFORM_PROPERTY); + return this; + } + public CustomerRequest selectName(){ + selectProperty(Customer.NAME_PROPERTY); + return this; + } + + /** + * fill the name with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS name) to fetch name property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public CustomerRequest unselectName(){ + unselectProperty(Customer.NAME_PROPERTY); + return this; + } + public CustomerRequest selectVersion(){ + selectProperty(Customer.VERSION_PROPERTY); + return this; + } + + /** + * fill the version with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS version) to fetch version property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public CustomerRequest unselectVersion(){ + unselectProperty(Customer.VERSION_PROPERTY); + return this; + } + public CustomerRequest selectCustomerOrderList(){ + return selectCustomerOrderListWith(Q.customerOrders().selectSelf()); + } + + public CustomerRequest selectCustomerOrderListWith(CustomerOrderRequest customerOrderList){ + enhanceRelation(Customer.CUSTOMER_ORDER_LIST_PROPERTY, customerOrderList, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, "selectCustomerOrderListWith", null, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest withId(Operator operator, Object... values){ + return appendSearchCriteria(createIdCriteria(operator, values)); + } + + public SearchCriteria createIdCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(Customer.ID_PROPERTY, operator, values); + } + + public CustomerRequest withIdIsNot(Long id){ + return withId(Operator.NOT_EQUAL, id); + } + + public CustomerRequest withIdIn(Long... id){ + return withId(Operator.IN, (Object[])id); + } + + public CustomerRequest withIdNotIn(Long... id){ + return withId(Operator.NOT_IN, (Object[])id); + } + public CustomerRequest withIdIs(Long id){ + return withId(Operator.EQUAL, id); + } + + + + public CustomerRequest filterByPlatform(Platform... platform){ + if (platform == null || platform.length == 0) { + throw new IllegalArgumentException("filterByPlatform parameter platform cannot be empty"); + } + return appendSearchCriteria(createPlatformCriteria(Operator.EQUAL, (Object[])platform)); + } + + public CustomerRequest withPlatform(Operator operator, Object... values){ + return appendSearchCriteria(createPlatformCriteria(operator, values)); + } + + public CustomerRequest withPlatformIsUnknown(){ + return withPlatform(Operator.IS_NULL); + } + + public CustomerRequest withPlatformIsKnown(){ + return withPlatform(Operator.IS_NOT_NULL); + } + + public SearchCriteria createPlatformCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(Customer.PLATFORM_PROPERTY, operator, values); + } + + public CustomerRequest filterByPlatform(Long platform){ + if(platform == null){ + return this; + } + return withPlatform(Operator.EQUAL, platform); + } + public CustomerRequest withPlatformMatching(PlatformRequest platform){ + return appendSearchCriteria(new SubQuerySearchCriteria(Customer.PLATFORM_PROPERTY, platform, Platform.ID_PROPERTY)); + } + + public CustomerRequest withoutPlatformMatching(PlatformRequest platform){ + return appendSearchCriteria(SearchCriteria.not( + new SubQuerySearchCriteria(Customer.PLATFORM_PROPERTY, platform, Platform.ID_PROPERTY))); + } + + public CustomerRequest filterByName(String... name){ + if (name == null || name.length == 0) { + throw new IllegalArgumentException("filterByName parameter name cannot be empty"); + } + return appendSearchCriteria(createNameCriteria(Operator.EQUAL, (Object[])name)); + } + + public CustomerRequest withName(Operator operator, Object... values){ + return appendSearchCriteria(createNameCriteria(operator, values)); + } + + public CustomerRequest withNameIsUnknown(){ + return withName(Operator.IS_NULL); + } + + public CustomerRequest withNameIsKnown(){ + return withName(Operator.IS_NOT_NULL); + } + + public SearchCriteria createNameCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(Customer.NAME_PROPERTY, operator, values); + } + + public CustomerRequest withNameIsNot(String name){ + return withName(Operator.NOT_EQUAL, name); + } + + public CustomerRequest withNameIn(String... name){ + return withName(Operator.IN, (Object[])name); + } + + public CustomerRequest withNameNotIn(String... name){ + return withName(Operator.NOT_IN, (Object[])name); + } + public CustomerRequest withNameGreaterThan(String name){ + return withName(Operator.GREATER_THAN, name); + } + + public CustomerRequest withNameGreaterThanOrEqualTo(String name){ + return withName(Operator.GREATER_THAN_OR_EQUAL, name); + } + + public CustomerRequest withNameLessThan(String name){ + return withName(Operator.LESS_THAN, name); + } + + public CustomerRequest withNameLessThanOrEqualTo(String name){ + return withName(Operator.LESS_THAN_OR_EQUAL, name); + } + + public CustomerRequest withNameBetween(String startOfName, String endOfName){ + return withName(Operator.BETWEEN, startOfName, endOfName); + } + public CustomerRequest withNameStartingWith(String name){ + return withName(Operator.BEGIN_WITH, name); + } + public CustomerRequest withNameContaining(String name){ + return withName(Operator.CONTAIN, name); + } + + public CustomerRequest withNameNotContaining(String name){ + return withName(Operator.NOT_CONTAIN, name); + } + + public CustomerRequest withNameNotStartingWith(String name){ + return withName(Operator.NOT_BEGIN_WITH, name); + } + + public CustomerRequest withNameEndingWith(String name){ + return withName(Operator.END_WITH, name); + } + + public CustomerRequest withNameNotEndingWith(String name){ + return withName(Operator.NOT_END_WITH, name); + } + + public CustomerRequest withNameIs(String name){ + return withName(Operator.EQUAL, name); + } + + public CustomerRequest withNameSoundingLike(String name){ + return withName(Operator.SOUNDS_LIKE, name); + } + + + + public CustomerRequest filterByVersion(Long... version){ + if (version == null || version.length == 0) { + throw new IllegalArgumentException("filterByVersion parameter version cannot be empty"); + } + return appendSearchCriteria(createVersionCriteria(Operator.EQUAL, (Object[])version)); + } + + public CustomerRequest withVersion(Operator operator, Object... values){ + return appendSearchCriteria(createVersionCriteria(operator, values)); + } + + public CustomerRequest withVersionIsUnknown(){ + return withVersion(Operator.IS_NULL); + } + + public CustomerRequest withVersionIsKnown(){ + return withVersion(Operator.IS_NOT_NULL); + } + + public SearchCriteria createVersionCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(Customer.VERSION_PROPERTY, operator, values); + } + + public CustomerRequest withVersionIs(Long version){ + return withVersion(Operator.EQUAL, version); + } + + public CustomerRequest withVersionIsNot(Long version){ + return withVersion(Operator.NOT_EQUAL, version); + } + + public CustomerRequest withVersionIn(Long... version){ + return withVersion(Operator.IN, (Object[])version); + } + + public CustomerRequest withVersionNotIn(Long... version){ + return withVersion(Operator.NOT_IN, (Object[])version); + } + public CustomerRequest withVersionGreaterThan(Long version){ + return withVersion(Operator.GREATER_THAN, version); + } + + public CustomerRequest withVersionGreaterThanOrEqualTo(Long version){ + return withVersion(Operator.GREATER_THAN_OR_EQUAL, version); + } + + public CustomerRequest withVersionLessThan(Long version){ + return withVersion(Operator.LESS_THAN, version); + } + + public CustomerRequest withVersionLessThanOrEqualTo(Long version){ + return withVersion(Operator.LESS_THAN_OR_EQUAL, version); + } + + public CustomerRequest withVersionBetween(Long startOfVersion, Long endOfVersion){ + return withVersion(Operator.BETWEEN, startOfVersion, endOfVersion); + } + + + public CustomerRequest withCustomerOrderListMatching(CustomerOrderRequest customerOrderRequest){ + return appendSearchCriteria(new SubQuerySearchCriteria(Customer.ID_PROPERTY, customerOrderRequest, CustomerOrder.CUSTOMER_PROPERTY)); + } + + public CustomerRequest withoutCustomerOrderListMatching(CustomerOrderRequest customerOrderRequest){ + return appendSearchCriteria(SearchCriteria.not(new SubQuerySearchCriteria(Customer.ID_PROPERTY, customerOrderRequest, CustomerOrder.CUSTOMER_PROPERTY))); + } + + public CustomerRequest haveCustomerOrders(){ + return withCustomerOrderListMatching(Q.customerOrders().unlimited()); + } + + public CustomerRequest haveNoCustomerOrders(){ + return withoutCustomerOrderListMatching(Q.customerOrders().unlimited()); + } + + public CustomerRequest count(){ + super.count(); + setPageCountOperation(io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.NAMED_COUNT, "count", null, null)); + return this; + } + public CustomerRequest countAs(String retName){ + super.count(retName); + setPageCountOperation(io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.NAMED_COUNT, "countAs", retName, null)); + return this; + } + public CustomerRequest groupByPlatformWithDetails(){ + return groupByPlatformWithDetails(Q.platforms().unlimited()); + } + + public CustomerRequest groupByPlatformWithDetails(PlatformRequest subRequest){ + aggregate(Customer.PLATFORM_PROPERTY, subRequest); + return this; + } + + + + public CustomerRequest groupByCustomerOrdersWithDetails(CustomerOrderRequest subRequest){ + aggregate(Customer.CUSTOMER_ORDER_LIST_PROPERTY, subRequest); + return this; + } + + public CustomerRequest groupById(){ + groupBy(Customer.ID_PROPERTY); + return this; + } + + public CustomerRequest groupByIdAs(String retName){ + groupBy(retName, Customer.ID_PROPERTY); + return this; + } + + public CustomerRequest groupByIdWithFunction(String retName, AggrFunction function){ + groupBy(retName, Customer.ID_PROPERTY, function); + return this; + } + public CustomerRequest groupByPlatformWith(PlatformRequest subRequest){ + groupBy(Customer.PLATFORM_PROPERTY, subRequest); + return this; + } + public CustomerRequest groupByPlatform(){ + groupBy(Customer.PLATFORM_PROPERTY); + return this; + } + + public CustomerRequest groupByPlatformAs(String retName){ + groupBy(retName, Customer.PLATFORM_PROPERTY); + return this; + } + + public CustomerRequest groupByPlatformWithFunction(String retName, AggrFunction function){ + groupBy(retName, Customer.PLATFORM_PROPERTY, function); + return this; + } + + public CustomerRequest groupByName(){ + groupBy(Customer.NAME_PROPERTY); + return this; + } + + public CustomerRequest groupByNameAs(String retName){ + groupBy(retName, Customer.NAME_PROPERTY); + return this; + } + + public CustomerRequest groupByNameWithFunction(String retName, AggrFunction function){ + groupBy(retName, Customer.NAME_PROPERTY, function); + return this; + } + + public CustomerRequest groupByVersion(){ + groupBy(Customer.VERSION_PROPERTY); + return this; + } + + public CustomerRequest groupByVersionAs(String retName){ + groupBy(retName, Customer.VERSION_PROPERTY); + return this; + } + + public CustomerRequest groupByVersionWithFunction(String retName, AggrFunction function){ + groupBy(retName, Customer.VERSION_PROPERTY, function); + return this; + } + + + + public CustomerRequest orderByIdAscending(){ + addOrderByAscending(Customer.ID_PROPERTY); + return this; + } + + public CustomerRequest orderByIdDescending(){ + addOrderByDescending(Customer.ID_PROPERTY); + return this; + } + + public CustomerRequest orderByPlatformAscending(){ + addOrderByAscending(Customer.PLATFORM_PROPERTY); + return this; + } + + public CustomerRequest orderByPlatformDescending(){ + addOrderByDescending(Customer.PLATFORM_PROPERTY); + return this; + } + + public CustomerRequest orderByNameAscending(){ + addOrderByAscending(Customer.NAME_PROPERTY); + return this; + } + + public CustomerRequest orderByNameDescending(){ + addOrderByDescending(Customer.NAME_PROPERTY); + return this; + } + public CustomerRequest orderByNameAscendingUsingGBK(){ + addOrderByAscendingUsingGBK(Customer.NAME_PROPERTY); + return this; + } + + public CustomerRequest orderByNameDescendingUsingGBK(){ + addOrderByDescendingUsingGBK(Customer.NAME_PROPERTY); + return this; + } + public CustomerRequest orderByVersionAscending(){ + addOrderByAscending(Customer.VERSION_PROPERTY); + return this; + } + + public CustomerRequest orderByVersionDescending(){ + addOrderByDescending(Customer.VERSION_PROPERTY); + return this; + } + + + public CustomerRequest statsFromCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + return statsFromCustomerOrdersAs(name, subRequest, false); + } + + public CustomerRequest statsFromCustomerOrdersAs(String name, CustomerOrderRequest subRequest, boolean singleResult){ + addRelationAggregate(name, subRequest, CustomerOrder.CUSTOMER_PROPERTY, singleResult, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "statsFromCustomerOrdersAs", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest statsFromCustomerOrders(CustomerOrderRequest subRequest){ + return statsFromCustomerOrdersAs(REFINEMENTS, subRequest); + } + public PlatformRequest rollUpToPlatform(){ + PlatformRequest platform = Q.platforms().unlimited(); + this.withPlatformMatching(platform) + .groupByPlatformWith(platform); + return platform; + } + + + + public CustomerRequest countCustomerOrders(){ + addRelationCount("Count", Q.customerOrders().unlimited(), CustomerOrder.CUSTOMER_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countCustomerOrders", null, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest countCustomerOrdersAs(String name){ + addRelationCount(name, Q.customerOrders().unlimited(), CustomerOrder.CUSTOMER_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countCustomerOrdersAs", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest countCustomerOrdersWith(String name, CustomerOrderRequest subRequest){ + addRelationCount(name, subRequest, CustomerOrder.CUSTOMER_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countCustomerOrdersWith", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public CustomerRequest minTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("minTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.CUSTOMER_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("min".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "minTotalAmountOfCustomerOrders", null, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest minTotalAmountOfCustomerOrdersAs(String name){ + return minTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public CustomerRequest minTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.CUSTOMER_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("min".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "minTotalAmountOfCustomerOrdersAs", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public CustomerRequest maxTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("maxTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.CUSTOMER_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("max".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "maxTotalAmountOfCustomerOrders", null, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest maxTotalAmountOfCustomerOrdersAs(String name){ + return maxTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public CustomerRequest maxTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.CUSTOMER_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("max".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "maxTotalAmountOfCustomerOrdersAs", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public CustomerRequest sumTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("sumTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.CUSTOMER_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("sum".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sumTotalAmountOfCustomerOrders", null, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest sumTotalAmountOfCustomerOrdersAs(String name){ + return sumTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public CustomerRequest sumTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.CUSTOMER_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("sum".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sumTotalAmountOfCustomerOrdersAs", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public CustomerRequest avgTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("avgTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.CUSTOMER_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("avg".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "avgTotalAmountOfCustomerOrders", null, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest avgTotalAmountOfCustomerOrdersAs(String name){ + return avgTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public CustomerRequest avgTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.CUSTOMER_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("avg".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "avgTotalAmountOfCustomerOrdersAs", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public CustomerRequest standardDeviationTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("stdDevTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.CUSTOMER_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "standardDeviationTotalAmountOfCustomerOrders", null, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest standardDeviationTotalAmountOfCustomerOrdersAs(String name){ + return standardDeviationTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public CustomerRequest standardDeviationTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.CUSTOMER_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "standardDeviationTotalAmountOfCustomerOrdersAs", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public CustomerRequest squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("stdDevPopTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.CUSTOMER_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrders", null, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrdersAs(String name){ + return squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public CustomerRequest squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.CUSTOMER_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrdersAs", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public CustomerRequest sampleVarianceTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("varSampTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.CUSTOMER_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_samp".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sampleVarianceTotalAmountOfCustomerOrders", null, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest sampleVarianceTotalAmountOfCustomerOrdersAs(String name){ + return sampleVarianceTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public CustomerRequest sampleVarianceTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.CUSTOMER_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_samp".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sampleVarianceTotalAmountOfCustomerOrdersAs", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public CustomerRequest samplePopulationVarianceTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("varPopTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.CUSTOMER_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "samplePopulationVarianceTotalAmountOfCustomerOrders", null, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest samplePopulationVarianceTotalAmountOfCustomerOrdersAs(String name){ + return samplePopulationVarianceTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public CustomerRequest samplePopulationVarianceTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.CUSTOMER_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "samplePopulationVarianceTotalAmountOfCustomerOrdersAs", name, Customer.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public CustomerRequest facetByPlatformAs(String facetName, PlatformRequest platform){ + return facetByPlatformAs(facetName, platform, true); + } + + public CustomerRequest facetByPlatformAs(String facetName, PlatformRequest platform, boolean includeAllFacets){ + addFacet(facetName, Customer.PLATFORM_PROPERTY, platform, includeAllFacets, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.FACET, "facetByPlatformAs", facetName, Customer.PLATFORM_PROPERTY)); + return this; + } + + + /** + * get topN records + * @param topN records number + */ + public CustomerRequest top(int topN) { + super.top(topN); + return this; + } + + /** Cross-runtime bounded-query alias. */ + public CustomerRequest limit(int limit) { + return top(limit); + } + + /** + * get records from offset(inclusive) to offset+size(exclusive) + * @param offset record offset + * @param size records number + */ + public CustomerRequest offset(int offset, int size) { + super.offset(offset, size); + return this; + } + + /** + * retrieve all records + */ + public CustomerRequest unlimited() { + super.unlimited(); + return this; + } + + /** + * get records of one page + * @param pageNumber page number(1-based) + * @param pageSize page size + */ + public CustomerRequest page(int pageNumber, int pageSize) { + int offset = (pageNumber - 1) * pageSize; + return offset(offset, pageSize); + } + + /** + * get records of one page, default page size is 10 + * @param pageNumber page number(1-based) + */ + public CustomerRequest page(int pageNumber) { + return page(pageNumber, 10); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrder.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrder.java new file mode 100644 index 00000000..98201887 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrder.java @@ -0,0 +1,223 @@ + +package com.example.requesttreeorderservice.customerorder; + +import com.example.requesttreeorderservice.Constants; +import com.example.requesttreeorderservice.customer.Customer; +import com.example.requesttreeorderservice.orderitem.OrderItem; +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import io.teaql.core.Audited; +import io.teaql.core.BaseEntity; +import io.teaql.core.EntityStatus; +import io.teaql.core.FieldLayout; +import io.teaql.core.FrameworkInternal; +import io.teaql.core.RemoteInput; +import io.teaql.core.SmartList; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Objects; + +/** + * [TEAQL AI WARNING] + * TeaQL was explicitly designed to PREVENT AI hallucinations and random guessing. + * DO NOT GUESS METHOD NAMES! + * The methods listed below are the ONLY valid ways to interact with this entity. + * If you encounter compilation errors (e.g., method not found), DO NOT guess another method name. + * Read the method signatures in this file before proceeding. + */ +public class CustomerOrder extends BaseEntity implements RemoteInput { + public static String INTERNAL_TYPE = "CustomerOrder"; + + // Framework metadata: complete fixed layout, independent of SQL/projection order. + public static final String __TEAQL_FIELD_LAYOUT_REVISION = "835b87e26813b0d288c20fbcd3ab52649d0d32277d4f1740fac610ad0b9432bb"; + public static final java.util.Map __TEAQL_FIXED_FIELD_INDEXES = + java.util.Map.ofEntries(java.util.Map.entry("id", 0), java.util.Map.entry("customer", 1), java.util.Map.entry("status", 2), java.util.Map.entry("order_number", 3), java.util.Map.entry("order_date", 4), java.util.Map.entry("total_amount", 5), java.util.Map.entry("version", 6)); + public static final java.util.Map> __TEAQL_FIXED_FIELD_MAPPINGS = + java.util.Map.ofEntries(java.util.Map.entry("id", java.util.List.of("id", "id")), java.util.Map.entry("customer", java.util.List.of("customer", "customer")), java.util.Map.entry("status", java.util.List.of("status", "status")), java.util.Map.entry("order_number", java.util.List.of("orderNumber", "order_number")), java.util.Map.entry("order_date", java.util.List.of("orderDate", "order_date")), java.util.Map.entry("total_amount", java.util.List.of("totalAmount", "total_amount")), java.util.Map.entry("version", java.util.List.of("version", "version"))); + public static final java.util.Set __TEAQL_FIXED_RELATION_NAMES = java.util.Set.of( + "orderItemList"); + public static final FieldLayout __TEAQL_FIELD_LAYOUT = + FieldLayout.installGenerated(FieldLayout.generated( + CustomerOrder.class, __TEAQL_FIELD_LAYOUT_REVISION, + __TEAQL_FIXED_FIELD_INDEXES, __TEAQL_FIXED_FIELD_MAPPINGS, __TEAQL_FIXED_RELATION_NAMES)); + + + public static final String CUSTOMER_PROPERTY = "customer"; + public static final String STATUS_PROPERTY = "status"; + public static final String ORDER_NUMBER_PROPERTY = "orderNumber"; + public static final String ORDER_DATE_PROPERTY = "orderDate"; + public static final String TOTAL_AMOUNT_PROPERTY = "totalAmount"; + public static final String ORDER_ITEM_LIST_PROPERTY = "orderItemList"; + private Customer customer; + + private OrderStatus status; + + private String orderNumber; + + private LocalDate orderDate; + + private BigDecimal totalAmount; + + private SmartList orderItemList; + + public Customer getCustomer(){ + return this.customer; + } + + public OrderStatus getStatus(){ + return this.status; + } + + public String getOrderNumber(){ + return this.orderNumber; + } + + public LocalDate getOrderDate(){ + return this.orderDate; + } + + public BigDecimal getTotalAmount(){ + return this.totalAmount; + } + + public SmartList getOrderItemList(){ + return this.orderItemList; + } + public CustomerOrder updateCustomer(Customer customer){ + if(Objects.equals(this.customer, customer)){ + return this; + } + handleUpdate(CUSTOMER_PROPERTY, getCustomer(), customer); + this.customer = customer; + return this; + } + + protected CustomerOrder updateStatus(OrderStatus status){ + if(Objects.equals(this.status, status)){ + return this; + } + handleUpdate(STATUS_PROPERTY, getStatus(), status); + this.status = status; + return this; + } + + public CustomerOrder updateOrderNumber(String orderNumber){ + orderNumber = (orderNumber == null ? null : orderNumber.trim()); + if(Objects.equals(this.orderNumber, orderNumber)){ + return this; + } + handleUpdate(ORDER_NUMBER_PROPERTY, getOrderNumber(), orderNumber); + this.orderNumber = orderNumber; + return this; + } + + public CustomerOrder updateOrderDate(LocalDate orderDate){ + if(Objects.equals(this.orderDate, orderDate)){ + return this; + } + handleUpdate(ORDER_DATE_PROPERTY, getOrderDate(), orderDate); + this.orderDate = orderDate; + return this; + } + + public CustomerOrder updateTotalAmount(BigDecimal totalAmount){ + if(Objects.equals(this.totalAmount, totalAmount)){ + return this; + } + handleUpdate(TOTAL_AMOUNT_PROPERTY, getTotalAmount(), totalAmount); + this.totalAmount = totalAmount; + return this; + } + + public CustomerOrder addOrderItem(OrderItem orderItem){ + if (orderItem == null){ + return this; + } + + if(null == this.orderItemList){ + this.orderItemList = new SmartList<>(); + } + + this.orderItemList.add(orderItem); + orderItem.updateCustomerOrder(this); + return this; + } + public boolean isStatusNew(){ + return Objects.equals(getStatus(), Constants.ORDER_STATUS_NEW); + } + + public CustomerOrder updateStatusToNew(){ + return updateStatus(Constants.ORDER_STATUS_NEW); + } + public boolean isStatusPaid(){ + return Objects.equals(getStatus(), Constants.ORDER_STATUS_PAID); + } + + public CustomerOrder updateStatusToPaid(){ + return updateStatus(Constants.ORDER_STATUS_PAID); + } + public boolean isStatusCancelled(){ + return Objects.equals(getStatus(), Constants.ORDER_STATUS_CANCELLED); + } + + public CustomerOrder updateStatusToCancelled(){ + return updateStatus(Constants.ORDER_STATUS_CANCELLED); + } + + public static CustomerOrder refer(Long id){ + CustomerOrder refer = new CustomerOrder(); + refer.__internalSet("id", id); + refer.set$status(EntityStatus.REFER); + return refer; + } + @Override + public String typeName(){ + return INTERNAL_TYPE; + } + + public CustomerOrder comment(String comment){ + this.setComment(comment); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public Audited auditAs(String action) { + return new Audited<>(this, action); + } + + // ===== Framework Internal: generated switch dispatch ===== + @Override + @FrameworkInternal + public void __internalSet(String property, Object value) { + markPropertyLoaded(property); + switch (property) { + case "customer": this.customer = (Customer) value; break; + + case "status": this.status = (OrderStatus) value; break; + + case "orderNumber": this.orderNumber = (value == null ? null : ((String)value).trim()); break; + + case "orderDate": this.orderDate = (LocalDate) value; break; + + case "totalAmount": this.totalAmount = (BigDecimal) value; break; + + case "orderItemList": this.orderItemList = (SmartList) value; break; + default: super.__internalSet(property, value); + } + } + + @Override + @FrameworkInternal + public Object __internalGet(String property) { + switch (property) { + case "customer": return this.customer; + case "status": return this.status; + case "orderNumber": return this.orderNumber; + case "orderDate": return this.orderDate; + case "totalAmount": return this.totalAmount; + case "orderItemList": return this.orderItemList; + default: return super.__internalGet(property); + } + } + +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderChecker.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderChecker.java new file mode 100644 index 00000000..21a40f68 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderChecker.java @@ -0,0 +1,97 @@ + +package com.example.requesttreeorderservice.customerorder; + +import com.example.requesttreeorderservice.customer.Customer; +import com.example.requesttreeorderservice.customer.CustomerChecker; +import com.example.requesttreeorderservice.orderitem.OrderItem; +import com.example.requesttreeorderservice.orderitem.OrderItemChecker; +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import com.example.requesttreeorderservice.orderstatus.OrderStatusChecker; +import io.teaql.core.UserContext; +import io.teaql.core.checker.Checker; +import io.teaql.core.checker.ObjectLocation; +import java.math.BigDecimal; +import java.time.LocalDate; + +public class CustomerOrderChecker implements Checker{ + + public String type(){ + return CustomerOrder.INTERNAL_TYPE; + } + + public void checkAndFix(UserContext _context, CustomerOrder customerOrder, ObjectLocation _parentLocation){ + if(needCheck(_context, customerOrder)){ + markAsChecked(_context, customerOrder); + doCheck(_context, customerOrder, _parentLocation); + } + } + + public void doCheck(UserContext _context, CustomerOrder customerOrder, ObjectLocation _parentLocation){ + if((customerOrder == null)){ + return; + } + if(customerOrder.newItem()){ + }else if(customerOrder.updateItem()){ + if(!customerOrder.isPropertyLoaded("customer")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "customer"), "Mutation requires a fully loaded entity"); + } + if(!customerOrder.isPropertyLoaded("status")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "status"), "Mutation requires a fully loaded entity"); + } + if(!customerOrder.isPropertyLoaded("orderNumber")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "order_number"), "Mutation requires a fully loaded entity"); + } + if(!customerOrder.isPropertyLoaded("orderDate")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "order_date"), "Mutation requires a fully loaded entity"); + } + if(!customerOrder.isPropertyLoaded("totalAmount")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "total_amount"), "Mutation requires a fully loaded entity"); + } + + } + checkCustomer(_context, customerOrder.getProperty(CustomerOrder.CUSTOMER_PROPERTY), newLocation(_parentLocation, "customer")); + checkStatus(_context, customerOrder.getProperty(CustomerOrder.STATUS_PROPERTY), newLocation(_parentLocation, "status")); + checkOrderNumber(_context, customerOrder.getProperty(CustomerOrder.ORDER_NUMBER_PROPERTY), newLocation(_parentLocation, "order_number")); + checkOrderDate(_context, customerOrder.getProperty(CustomerOrder.ORDER_DATE_PROPERTY), newLocation(_parentLocation, "order_date")); + checkTotalAmount(_context, customerOrder.getProperty(CustomerOrder.TOTAL_AMOUNT_PROPERTY), newLocation(_parentLocation, "total_amount")); + for(int i = 0; customerOrder.getOrderItemList() != null && i < customerOrder.getOrderItemList().size(); i++){ + OrderItem orderItem = customerOrder.getOrderItemList().get(i); + new OrderItemChecker().checkAndFix(_context, orderItem, newLocation(_parentLocation, "order_item_list", i)); + } + } + + public void checkCustomer(UserContext _context, Customer customer, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, customer); + if((customer == null)){ + return; + } + new CustomerChecker().checkAndFix(_context, customer, _parentLocation); + } + public void checkStatus(UserContext _context, OrderStatus status, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, status); + if((status == null)){ + return; + } + new OrderStatusChecker().checkAndFix(_context, status, _parentLocation); + } + public void checkOrderNumber(UserContext _context, String orderNumber, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, orderNumber); + if((orderNumber == null)){ + return; + } + maxStringCheck(_context, _parentLocation, 100, orderNumber); + + } + public void checkOrderDate(UserContext _context, LocalDate orderDate, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, orderDate); + if((orderDate == null)){ + return; + } + } + public void checkTotalAmount(UserContext _context, BigDecimal totalAmount, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, totalAmount); + if((totalAmount == null)){ + return; + } + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderExpression.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderExpression.java new file mode 100644 index 00000000..b75e95a4 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderExpression.java @@ -0,0 +1,93 @@ + +package com.example.requesttreeorderservice.customerorder; + +import com.example.requesttreeorderservice.customer.Customer; +import com.example.requesttreeorderservice.customer.CustomerExpression; +import com.example.requesttreeorderservice.orderitem.OrderItem; +import com.example.requesttreeorderservice.orderitem.OrderItemListExpression; +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import com.example.requesttreeorderservice.orderstatus.OrderStatusExpression; +import io.teaql.core.UserContext; +import io.teaql.core.value.BaseEntityExpression; +import io.teaql.core.value.Expression; +import io.teaql.core.value.ExpressionAdaptor; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.function.Function; + +public class CustomerOrderExpression extends ExpressionAdaptor implements BaseEntityExpression { + public CustomerOrderExpression(Expression expression){ + super(expression); + } + + public CustomerOrderExpression(Expression expression, Function function){ + super(expression, function); + } + + public CustomerOrderExpression updateId(Long id){ + return new CustomerOrderExpression(this, $it -> {((CustomerOrder)$it).__internalSet("id", id); return this;}); + } + + public CustomerOrderExpression save(UserContext userContext){ + return new CustomerOrderExpression(this, $it -> ((CustomerOrder)$it).auditAs("Saved by Expression").save(userContext)); + } + + public CustomerOrderExpression save(String intent, UserContext userContext){ + return new CustomerOrderExpression(this, $it -> ((CustomerOrder)$it).auditAs(intent).save(userContext)); + } + + public boolean isNull() { + return resolve() == null; + } + + + public CustomerExpression getCustomer(){ + return new CustomerExpression(loaded("customer", CustomerOrder::getCustomer)); + } + + public CustomerOrderExpression updateCustomer(Customer customer){ + return new CustomerOrderExpression(this, $it -> ((CustomerOrder)$it).updateCustomer(customer)); + } + + public OrderStatusExpression getStatus(){ + return new OrderStatusExpression(loaded("status", CustomerOrder::getStatus)); + } + + public CustomerOrderExpression updateStatusToNew(){ + return new CustomerOrderExpression(this, $it -> ((CustomerOrder)$it).updateStatusToNew()); + } + public CustomerOrderExpression updateStatusToPaid(){ + return new CustomerOrderExpression(this, $it -> ((CustomerOrder)$it).updateStatusToPaid()); + } + public CustomerOrderExpression updateStatusToCancelled(){ + return new CustomerOrderExpression(this, $it -> ((CustomerOrder)$it).updateStatusToCancelled()); + } + + public Expression getOrderNumber(){ + return loaded("orderNumber", CustomerOrder::getOrderNumber); + } + public CustomerOrderExpression updateOrderNumber(String orderNumber){ + return new CustomerOrderExpression(this, $it -> ((CustomerOrder)$it).updateOrderNumber(orderNumber)); + } + + public Expression getOrderDate(){ + return loaded("orderDate", CustomerOrder::getOrderDate); + } + public CustomerOrderExpression updateOrderDate(LocalDate orderDate){ + return new CustomerOrderExpression(this, $it -> ((CustomerOrder)$it).updateOrderDate(orderDate)); + } + + public Expression getTotalAmount(){ + return loaded("totalAmount", CustomerOrder::getTotalAmount); + } + public CustomerOrderExpression updateTotalAmount(BigDecimal totalAmount){ + return new CustomerOrderExpression(this, $it -> ((CustomerOrder)$it).updateTotalAmount(totalAmount)); + } + + public OrderItemListExpression getOrderItemList(){ + return new OrderItemListExpression(loaded("orderItemList", CustomerOrder::getOrderItemList)); + } + public CustomerOrderExpression addOrderItem(OrderItem orderItem){ + return new CustomerOrderExpression(this, $it -> ((CustomerOrder)$it).addOrderItem(orderItem)); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderListExpression.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderListExpression.java new file mode 100644 index 00000000..959f5259 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderListExpression.java @@ -0,0 +1,25 @@ + +package com.example.requesttreeorderservice.customerorder; + +import io.teaql.core.SmartList; +import io.teaql.core.value.Expression; +import io.teaql.core.value.SmartListExpression; +import java.util.function.Function; + +public class CustomerOrderListExpression extends SmartListExpression { + public CustomerOrderListExpression(Expression> expression){ + super(expression); + } + + public CustomerOrderListExpression(Expression expression, Function> function){ + super(expression, function); + } + + public CustomerOrderExpression first() { + return new CustomerOrderExpression(super.first()); + } + + public CustomerOrderExpression get(int index) { + return new CustomerOrderExpression(super.get(index)); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderRequest.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderRequest.java new file mode 100644 index 00000000..1f0b6485 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/customerorder/CustomerOrderRequest.java @@ -0,0 +1,1438 @@ + +package com.example.requesttreeorderservice.customerorder; + +import com.example.requesttreeorderservice.Q; +import com.example.requesttreeorderservice.customer.Customer; +import com.example.requesttreeorderservice.customer.CustomerRequest; +import com.example.requesttreeorderservice.orderitem.OrderItem; +import com.example.requesttreeorderservice.orderitem.OrderItemRequest; +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import com.example.requesttreeorderservice.orderstatus.OrderStatusRequest; +import io.teaql.core.AggrFunction; +import io.teaql.core.BaseRequest; +import io.teaql.core.PropertyReference; +import io.teaql.core.SearchCriteria; +import io.teaql.core.SubQuerySearchCriteria; +import io.teaql.core.criteria.Operator; +import io.teaql.core.criteria.TwoOperatorCriteria; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Date; + +public class CustomerOrderRequest extends BaseRequest { + + /** + * @deprecated AI agents and business code must use the generated Q facade + * instead of constructing request builders directly. + */ + @Deprecated + @SuppressWarnings("unchecked") + public CustomerOrderRequest(Class returnType){ + this(returnType, () -> (T) new CustomerOrder()); + } + + protected CustomerOrderRequest(Class returnType, java.util.function.Supplier factory){ + super(returnType, factory); + selectId(); + selectVersion(); + } + + public CustomerOrderRequest comment(String comment){ + super.internalComment(comment); + return this; + } + + // purpose() 继承自 BaseRequest,返回 ExecutableRequest(终结方法) + + public CustomerOrderRequest returnType(Class returnType){ + super.setReturnType(returnType); + return this; + } + + public CustomerOrderRequest enableAggregationCache(long cacheExpiredMillis){ + super.enableAggregationCache(); + super.aggregateCacheTime(cacheExpiredMillis); + return this; + } + + public CustomerOrderRequest enableAggregationCache(){ + return enableAggregationCache(0l); + } + + + public CustomerOrderRequest propagateAggregationCache(long cacheExpiredMillis){ + super.propagateAggregationCache(cacheExpiredMillis); + return this; + } + + /** + * Accept best-effort stateful seek optimization for browsing consecutive pages. + * Do not use this for business processing that must visit every row exactly once. + */ + public CustomerOrderRequest optimizeForContinuousPageFetch(){ + super.optimizeForContinuousPageFetch(); + return this; + } + + public CustomerOrderRequest optimizeForContinuousPageFetch(String namespace, int ttlSeconds){ + super.optimizeForContinuousPageFetch(namespace, ttlSeconds); + return this; + } + + public CustomerOrderRequest optimizePaginationWithIdSet(){ + super.optimizePaginationWithIdSet(); + return this; + } + + public CustomerOrderRequest optimizePaginationWithIdSet( + String namespace, int ttlSeconds, int maxIds){ + super.optimizePaginationWithIdSet(namespace, ttlSeconds, maxIds); + return this; + } + + public CustomerOrderRequest topNProbeParentThreshold(int threshold){ + super.topNProbeParentThreshold(threshold); + return this; + } + + public CustomerOrderRequest appendSearchCriteria(SearchCriteria searchCriteria){ + return (CustomerOrderRequest)super.appendSearchCriteria(searchCriteria); + } + + public CustomerOrderRequest filter(String property1, Operator operator, String property2){ + return appendSearchCriteria(new TwoOperatorCriteria(operator, new PropertyReference(property1), new PropertyReference(property2))); + } + + + public CustomerOrderRequest matchingAnyOf(CustomerOrderRequest customerOrder){ + super.internalMatchAny(customerOrder); + return this; + } + + public CustomerOrderRequest enhanceChildrenIfNeeded(){ + return this; + } + + public CustomerOrderRequest withDeletedRows(){ + super.withDeletedRows(); + return this; + } + + public CustomerOrderRequest deletedRowsOnly(){ + super.deletedRowsOnly(); + return this; + } + + public CustomerOrderRequest selectSelf(){ + super.selectSelf(); + return selectId().selectCustomerIdOnly().selectStatusIdOnly().selectOrderNumber().selectOrderDate().selectTotalAmount().selectVersion(); + } + + public CustomerOrderRequest selectSelfFields(){ + return selectSelf(); + } + + public CustomerOrderRequest selectSelfWithoutParent(){ + selectId(); + selectVersion(); + selectProperty(CustomerOrder.ID_PROPERTY); + selectProperty(CustomerOrder.CUSTOMER_PROPERTY); + selectProperty(CustomerOrder.STATUS_PROPERTY); + selectProperty(CustomerOrder.ORDER_NUMBER_PROPERTY); + selectProperty(CustomerOrder.ORDER_DATE_PROPERTY); + selectProperty(CustomerOrder.TOTAL_AMOUNT_PROPERTY); + selectProperty(CustomerOrder.VERSION_PROPERTY); + return this; + } + + public CustomerOrderRequest selectAll(){ + super.selectAll(); + return selectId().selectCustomer().selectStatus().selectOrderNumber().selectOrderDate().selectTotalAmount().selectVersion(); + } + + public CustomerOrderRequest selectChildren(){ + super.selectAny(); + selectOrderItemList(); + return selectId().selectCustomer().selectStatus().selectOrderNumber().selectOrderDate().selectTotalAmount().selectVersion(); + } + + + public CustomerOrderRequest selectId(){ + selectProperty(CustomerOrder.ID_PROPERTY); + return this; + } + + /** + * fill the id with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS id) to fetch id property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public CustomerOrderRequest unselectId(){ + unselectProperty(CustomerOrder.ID_PROPERTY); + return this; + } + public CustomerOrderRequest selectCustomerIdOnly(){ + selectProperty(CustomerOrder.CUSTOMER_PROPERTY); + return this; + } + + public CustomerOrderRequest selectCustomer(){ + return selectCustomerWith(Q.customers().unlimited().selectSelf()); + } + + public CustomerOrderRequest selectCustomerWith(CustomerRequest customer){ + selectProperty(CustomerOrder.CUSTOMER_PROPERTY); + enhanceRelation(CustomerOrder.CUSTOMER_PROPERTY, customer, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, "selectCustomerWith", null, CustomerOrder.CUSTOMER_PROPERTY)); + return this; + } + + public CustomerOrderRequest unselectCustomer(){ + unselectProperty(CustomerOrder.CUSTOMER_PROPERTY); + return this; + } + public CustomerOrderRequest selectStatusIdOnly(){ + selectProperty(CustomerOrder.STATUS_PROPERTY); + return this; + } + + public CustomerOrderRequest selectStatus(){ + return selectStatusWith(Q.orderStatuses().unlimited().selectSelf()); + } + + public CustomerOrderRequest selectStatusWith(OrderStatusRequest status){ + selectProperty(CustomerOrder.STATUS_PROPERTY); + enhanceRelation(CustomerOrder.STATUS_PROPERTY, status, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, "selectStatusWith", null, CustomerOrder.STATUS_PROPERTY)); + return this; + } + + public CustomerOrderRequest unselectStatus(){ + unselectProperty(CustomerOrder.STATUS_PROPERTY); + return this; + } + public CustomerOrderRequest selectOrderNumber(){ + selectProperty(CustomerOrder.ORDER_NUMBER_PROPERTY); + return this; + } + + /** + * fill the orderNumber with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS orderNumber) to fetch orderNumber property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public CustomerOrderRequest unselectOrderNumber(){ + unselectProperty(CustomerOrder.ORDER_NUMBER_PROPERTY); + return this; + } + public CustomerOrderRequest selectOrderDate(){ + selectProperty(CustomerOrder.ORDER_DATE_PROPERTY); + return this; + } + + /** + * fill the orderDate with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS orderDate) to fetch orderDate property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public CustomerOrderRequest unselectOrderDate(){ + unselectProperty(CustomerOrder.ORDER_DATE_PROPERTY); + return this; + } + public CustomerOrderRequest selectTotalAmount(){ + selectProperty(CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + + /** + * fill the totalAmount with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS totalAmount) to fetch totalAmount property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + /** + * fill the totalAmount with customized aggrFunction, TEAQL uses ({aggrFunction}(totalAmount) AS totalAmount to fetch totalAmount property. + * @param aggrFunction aggrFunction + */ + public CustomerOrderRequest selectTotalAmount(AggrFunction aggrFunction){ + selectProperty(CustomerOrder.TOTAL_AMOUNT_PROPERTY, aggrFunction); + return this; + } + + + public CustomerOrderRequest unselectTotalAmount(){ + unselectProperty(CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + public CustomerOrderRequest selectVersion(){ + selectProperty(CustomerOrder.VERSION_PROPERTY); + return this; + } + + /** + * fill the version with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS version) to fetch version property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public CustomerOrderRequest unselectVersion(){ + unselectProperty(CustomerOrder.VERSION_PROPERTY); + return this; + } + public CustomerOrderRequest selectOrderItemList(){ + return selectOrderItemListWith(Q.orderItems().selectSelf()); + } + + public CustomerOrderRequest selectOrderItemListWith(OrderItemRequest orderItemList){ + enhanceRelation(CustomerOrder.ORDER_ITEM_LIST_PROPERTY, orderItemList, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, "selectOrderItemListWith", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest withId(Operator operator, Object... values){ + return appendSearchCriteria(createIdCriteria(operator, values)); + } + + public SearchCriteria createIdCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(CustomerOrder.ID_PROPERTY, operator, values); + } + + public CustomerOrderRequest withIdIsNot(Long id){ + return withId(Operator.NOT_EQUAL, id); + } + + public CustomerOrderRequest withIdIn(Long... id){ + return withId(Operator.IN, (Object[])id); + } + + public CustomerOrderRequest withIdNotIn(Long... id){ + return withId(Operator.NOT_IN, (Object[])id); + } + public CustomerOrderRequest withIdIs(Long id){ + return withId(Operator.EQUAL, id); + } + + + + public CustomerOrderRequest filterByCustomer(Customer... customer){ + if (customer == null || customer.length == 0) { + throw new IllegalArgumentException("filterByCustomer parameter customer cannot be empty"); + } + return appendSearchCriteria(createCustomerCriteria(Operator.EQUAL, (Object[])customer)); + } + + public CustomerOrderRequest withCustomer(Operator operator, Object... values){ + return appendSearchCriteria(createCustomerCriteria(operator, values)); + } + + public CustomerOrderRequest withCustomerIsUnknown(){ + return withCustomer(Operator.IS_NULL); + } + + public CustomerOrderRequest withCustomerIsKnown(){ + return withCustomer(Operator.IS_NOT_NULL); + } + + public SearchCriteria createCustomerCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(CustomerOrder.CUSTOMER_PROPERTY, operator, values); + } + + public CustomerOrderRequest filterByCustomer(Long customer){ + if(customer == null){ + return this; + } + return withCustomer(Operator.EQUAL, customer); + } + public CustomerOrderRequest withCustomerMatching(CustomerRequest customer){ + return appendSearchCriteria(new SubQuerySearchCriteria(CustomerOrder.CUSTOMER_PROPERTY, customer, Customer.ID_PROPERTY)); + } + + public CustomerOrderRequest withoutCustomerMatching(CustomerRequest customer){ + return appendSearchCriteria(SearchCriteria.not( + new SubQuerySearchCriteria(CustomerOrder.CUSTOMER_PROPERTY, customer, Customer.ID_PROPERTY))); + } + + public CustomerOrderRequest filterByStatus(OrderStatus... status){ + if (status == null || status.length == 0) { + throw new IllegalArgumentException("filterByStatus parameter status cannot be empty"); + } + return appendSearchCriteria(createStatusCriteria(Operator.EQUAL, (Object[])status)); + } + + public CustomerOrderRequest withStatus(Operator operator, Object... values){ + return appendSearchCriteria(createStatusCriteria(operator, values)); + } + + public CustomerOrderRequest withStatusIsUnknown(){ + return withStatus(Operator.IS_NULL); + } + + public CustomerOrderRequest withStatusIsKnown(){ + return withStatus(Operator.IS_NOT_NULL); + } + + public SearchCriteria createStatusCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(CustomerOrder.STATUS_PROPERTY, operator, values); + } + + public CustomerOrderRequest filterByStatus(Long status){ + if(status == null){ + return this; + } + return withStatus(Operator.EQUAL, status); + } + public CustomerOrderRequest withStatusMatching(OrderStatusRequest status){ + return appendSearchCriteria(new SubQuerySearchCriteria(CustomerOrder.STATUS_PROPERTY, status, OrderStatus.ID_PROPERTY)); + } + + public CustomerOrderRequest withoutStatusMatching(OrderStatusRequest status){ + return appendSearchCriteria(SearchCriteria.not( + new SubQuerySearchCriteria(CustomerOrder.STATUS_PROPERTY, status, OrderStatus.ID_PROPERTY))); + } + + public CustomerOrderRequest filterByOrderNumber(String... orderNumber){ + if (orderNumber == null || orderNumber.length == 0) { + throw new IllegalArgumentException("filterByOrderNumber parameter orderNumber cannot be empty"); + } + return appendSearchCriteria(createOrderNumberCriteria(Operator.EQUAL, (Object[])orderNumber)); + } + + public CustomerOrderRequest withOrderNumber(Operator operator, Object... values){ + return appendSearchCriteria(createOrderNumberCriteria(operator, values)); + } + + public CustomerOrderRequest withOrderNumberIsUnknown(){ + return withOrderNumber(Operator.IS_NULL); + } + + public CustomerOrderRequest withOrderNumberIsKnown(){ + return withOrderNumber(Operator.IS_NOT_NULL); + } + + public SearchCriteria createOrderNumberCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(CustomerOrder.ORDER_NUMBER_PROPERTY, operator, values); + } + + public CustomerOrderRequest withOrderNumberIsNot(String orderNumber){ + return withOrderNumber(Operator.NOT_EQUAL, orderNumber); + } + + public CustomerOrderRequest withOrderNumberIn(String... orderNumber){ + return withOrderNumber(Operator.IN, (Object[])orderNumber); + } + + public CustomerOrderRequest withOrderNumberNotIn(String... orderNumber){ + return withOrderNumber(Operator.NOT_IN, (Object[])orderNumber); + } + public CustomerOrderRequest withOrderNumberGreaterThan(String orderNumber){ + return withOrderNumber(Operator.GREATER_THAN, orderNumber); + } + + public CustomerOrderRequest withOrderNumberGreaterThanOrEqualTo(String orderNumber){ + return withOrderNumber(Operator.GREATER_THAN_OR_EQUAL, orderNumber); + } + + public CustomerOrderRequest withOrderNumberLessThan(String orderNumber){ + return withOrderNumber(Operator.LESS_THAN, orderNumber); + } + + public CustomerOrderRequest withOrderNumberLessThanOrEqualTo(String orderNumber){ + return withOrderNumber(Operator.LESS_THAN_OR_EQUAL, orderNumber); + } + + public CustomerOrderRequest withOrderNumberBetween(String startOfOrderNumber, String endOfOrderNumber){ + return withOrderNumber(Operator.BETWEEN, startOfOrderNumber, endOfOrderNumber); + } + public CustomerOrderRequest withOrderNumberStartingWith(String orderNumber){ + return withOrderNumber(Operator.BEGIN_WITH, orderNumber); + } + public CustomerOrderRequest withOrderNumberContaining(String orderNumber){ + return withOrderNumber(Operator.CONTAIN, orderNumber); + } + + public CustomerOrderRequest withOrderNumberNotContaining(String orderNumber){ + return withOrderNumber(Operator.NOT_CONTAIN, orderNumber); + } + + public CustomerOrderRequest withOrderNumberNotStartingWith(String orderNumber){ + return withOrderNumber(Operator.NOT_BEGIN_WITH, orderNumber); + } + + public CustomerOrderRequest withOrderNumberEndingWith(String orderNumber){ + return withOrderNumber(Operator.END_WITH, orderNumber); + } + + public CustomerOrderRequest withOrderNumberNotEndingWith(String orderNumber){ + return withOrderNumber(Operator.NOT_END_WITH, orderNumber); + } + + public CustomerOrderRequest withOrderNumberIs(String orderNumber){ + return withOrderNumber(Operator.EQUAL, orderNumber); + } + + public CustomerOrderRequest withOrderNumberSoundingLike(String orderNumber){ + return withOrderNumber(Operator.SOUNDS_LIKE, orderNumber); + } + + + + public CustomerOrderRequest filterByOrderDate(LocalDate... orderDate){ + if (orderDate == null || orderDate.length == 0) { + throw new IllegalArgumentException("filterByOrderDate parameter orderDate cannot be empty"); + } + return appendSearchCriteria(createOrderDateCriteria(Operator.EQUAL, (Object[])orderDate)); + } + + public CustomerOrderRequest withOrderDate(Operator operator, Object... values){ + return appendSearchCriteria(createOrderDateCriteria(operator, values)); + } + + public CustomerOrderRequest withOrderDateIsUnknown(){ + return withOrderDate(Operator.IS_NULL); + } + + public CustomerOrderRequest withOrderDateIsKnown(){ + return withOrderDate(Operator.IS_NOT_NULL); + } + + public SearchCriteria createOrderDateCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(CustomerOrder.ORDER_DATE_PROPERTY, operator, values); + } + + public CustomerOrderRequest withOrderDateIs(LocalDate orderDate){ + return withOrderDate(Operator.EQUAL, orderDate); + } + + public CustomerOrderRequest withOrderDateIsNot(LocalDate orderDate){ + return withOrderDate(Operator.NOT_EQUAL, orderDate); + } + + public CustomerOrderRequest withOrderDateIn(LocalDate... orderDate){ + return withOrderDate(Operator.IN, (Object[])orderDate); + } + + public CustomerOrderRequest withOrderDateNotIn(LocalDate... orderDate){ + return withOrderDate(Operator.NOT_IN, (Object[])orderDate); + } + public CustomerOrderRequest withOrderDateGreaterThan(LocalDate orderDate){ + return withOrderDate(Operator.GREATER_THAN, orderDate); + } + + public CustomerOrderRequest withOrderDateGreaterThanOrEqualTo(LocalDate orderDate){ + return withOrderDate(Operator.GREATER_THAN_OR_EQUAL, orderDate); + } + + public CustomerOrderRequest withOrderDateLessThan(LocalDate orderDate){ + return withOrderDate(Operator.LESS_THAN, orderDate); + } + + public CustomerOrderRequest withOrderDateLessThanOrEqualTo(LocalDate orderDate){ + return withOrderDate(Operator.LESS_THAN_OR_EQUAL, orderDate); + } + + public CustomerOrderRequest withOrderDateBetween(LocalDate startOfOrderDate, LocalDate endOfOrderDate){ + return withOrderDate(Operator.BETWEEN, startOfOrderDate, endOfOrderDate); + } + public CustomerOrderRequest withOrderDateBefore(LocalDate orderDate){ + return withOrderDate(Operator.LESS_THAN, orderDate); + } + + public CustomerOrderRequest withOrderDateBefore(Date orderDate){ + return withOrderDate(Operator.LESS_THAN, orderDate); + } + + public CustomerOrderRequest withOrderDateAfter(LocalDate orderDate){ + return withOrderDate(Operator.GREATER_THAN, orderDate); + } + + public CustomerOrderRequest withOrderDateAfter(Date orderDate){ + return withOrderDate(Operator.GREATER_THAN, orderDate); + } + + public CustomerOrderRequest withOrderDateBetween(Date startOfOrderDate, Date endOfOrderDate){ + return withOrderDate(Operator.BETWEEN, startOfOrderDate, endOfOrderDate); + } + + + + + public CustomerOrderRequest filterByTotalAmount(BigDecimal... totalAmount){ + if (totalAmount == null || totalAmount.length == 0) { + throw new IllegalArgumentException("filterByTotalAmount parameter totalAmount cannot be empty"); + } + return appendSearchCriteria(createTotalAmountCriteria(Operator.EQUAL, (Object[])totalAmount)); + } + + public CustomerOrderRequest withTotalAmount(Operator operator, Object... values){ + return appendSearchCriteria(createTotalAmountCriteria(operator, values)); + } + + public CustomerOrderRequest withTotalAmountIsUnknown(){ + return withTotalAmount(Operator.IS_NULL); + } + + public CustomerOrderRequest withTotalAmountIsKnown(){ + return withTotalAmount(Operator.IS_NOT_NULL); + } + + public SearchCriteria createTotalAmountCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(CustomerOrder.TOTAL_AMOUNT_PROPERTY, operator, values); + } + + public CustomerOrderRequest withTotalAmountIs(BigDecimal totalAmount){ + return withTotalAmount(Operator.EQUAL, totalAmount); + } + + public CustomerOrderRequest withTotalAmountIsNot(BigDecimal totalAmount){ + return withTotalAmount(Operator.NOT_EQUAL, totalAmount); + } + + public CustomerOrderRequest withTotalAmountIn(BigDecimal... totalAmount){ + return withTotalAmount(Operator.IN, (Object[])totalAmount); + } + + public CustomerOrderRequest withTotalAmountNotIn(BigDecimal... totalAmount){ + return withTotalAmount(Operator.NOT_IN, (Object[])totalAmount); + } + public CustomerOrderRequest withTotalAmountGreaterThan(BigDecimal totalAmount){ + return withTotalAmount(Operator.GREATER_THAN, totalAmount); + } + + public CustomerOrderRequest withTotalAmountGreaterThanOrEqualTo(BigDecimal totalAmount){ + return withTotalAmount(Operator.GREATER_THAN_OR_EQUAL, totalAmount); + } + + public CustomerOrderRequest withTotalAmountLessThan(BigDecimal totalAmount){ + return withTotalAmount(Operator.LESS_THAN, totalAmount); + } + + public CustomerOrderRequest withTotalAmountLessThanOrEqualTo(BigDecimal totalAmount){ + return withTotalAmount(Operator.LESS_THAN_OR_EQUAL, totalAmount); + } + + public CustomerOrderRequest withTotalAmountBetween(BigDecimal startOfTotalAmount, BigDecimal endOfTotalAmount){ + return withTotalAmount(Operator.BETWEEN, startOfTotalAmount, endOfTotalAmount); + } + + + + public CustomerOrderRequest filterByVersion(Long... version){ + if (version == null || version.length == 0) { + throw new IllegalArgumentException("filterByVersion parameter version cannot be empty"); + } + return appendSearchCriteria(createVersionCriteria(Operator.EQUAL, (Object[])version)); + } + + public CustomerOrderRequest withVersion(Operator operator, Object... values){ + return appendSearchCriteria(createVersionCriteria(operator, values)); + } + + public CustomerOrderRequest withVersionIsUnknown(){ + return withVersion(Operator.IS_NULL); + } + + public CustomerOrderRequest withVersionIsKnown(){ + return withVersion(Operator.IS_NOT_NULL); + } + + public SearchCriteria createVersionCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(CustomerOrder.VERSION_PROPERTY, operator, values); + } + + public CustomerOrderRequest withVersionIs(Long version){ + return withVersion(Operator.EQUAL, version); + } + + public CustomerOrderRequest withVersionIsNot(Long version){ + return withVersion(Operator.NOT_EQUAL, version); + } + + public CustomerOrderRequest withVersionIn(Long... version){ + return withVersion(Operator.IN, (Object[])version); + } + + public CustomerOrderRequest withVersionNotIn(Long... version){ + return withVersion(Operator.NOT_IN, (Object[])version); + } + public CustomerOrderRequest withVersionGreaterThan(Long version){ + return withVersion(Operator.GREATER_THAN, version); + } + + public CustomerOrderRequest withVersionGreaterThanOrEqualTo(Long version){ + return withVersion(Operator.GREATER_THAN_OR_EQUAL, version); + } + + public CustomerOrderRequest withVersionLessThan(Long version){ + return withVersion(Operator.LESS_THAN, version); + } + + public CustomerOrderRequest withVersionLessThanOrEqualTo(Long version){ + return withVersion(Operator.LESS_THAN_OR_EQUAL, version); + } + + public CustomerOrderRequest withVersionBetween(Long startOfVersion, Long endOfVersion){ + return withVersion(Operator.BETWEEN, startOfVersion, endOfVersion); + } + + + public CustomerOrderRequest withOrderItemListMatching(OrderItemRequest orderItemRequest){ + return appendSearchCriteria(new SubQuerySearchCriteria(CustomerOrder.ID_PROPERTY, orderItemRequest, OrderItem.CUSTOMER_ORDER_PROPERTY)); + } + + public CustomerOrderRequest withoutOrderItemListMatching(OrderItemRequest orderItemRequest){ + return appendSearchCriteria(SearchCriteria.not(new SubQuerySearchCriteria(CustomerOrder.ID_PROPERTY, orderItemRequest, OrderItem.CUSTOMER_ORDER_PROPERTY))); + } + + public CustomerOrderRequest haveOrderItems(){ + return withOrderItemListMatching(Q.orderItems().unlimited()); + } + + public CustomerOrderRequest haveNoOrderItems(){ + return withoutOrderItemListMatching(Q.orderItems().unlimited()); + } + + public CustomerOrderRequest count(){ + super.count(); + setPageCountOperation(io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.NAMED_COUNT, "count", null, null)); + return this; + } + public CustomerOrderRequest countAs(String retName){ + super.count(retName); + setPageCountOperation(io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.NAMED_COUNT, "countAs", retName, null)); + return this; + } + public CustomerOrderRequest minTotalAmount(){ + return minTotalAmountAs(prefix("minOf",CustomerOrder.TOTAL_AMOUNT_PROPERTY)); + } + + public CustomerOrderRequest minTotalAmountAs(String retName){ + super.min(retName, CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + public CustomerOrderRequest maxTotalAmount(){ + return maxTotalAmountAs(prefix("maxOf",CustomerOrder.TOTAL_AMOUNT_PROPERTY)); + } + + public CustomerOrderRequest maxTotalAmountAs(String retName){ + super.max(retName, CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + public CustomerOrderRequest sumTotalAmount(){ + return sumTotalAmountAs(prefix("sumOf",CustomerOrder.TOTAL_AMOUNT_PROPERTY)); + } + + public CustomerOrderRequest sumTotalAmountAs(String retName){ + super.sum(retName, CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + public CustomerOrderRequest avgTotalAmount(){ + return avgTotalAmountAs(prefix("avgOf",CustomerOrder.TOTAL_AMOUNT_PROPERTY)); + } + + public CustomerOrderRequest avgTotalAmountAs(String retName){ + super.avg(retName, CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + public CustomerOrderRequest standardDeviationTotalAmount(){ + return standardDeviationTotalAmountAs(prefix("standardDeviationOf",CustomerOrder.TOTAL_AMOUNT_PROPERTY)); + } + + public CustomerOrderRequest standardDeviationTotalAmountAs(String retName){ + super.standardDeviation(retName, CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + public CustomerOrderRequest squareRootOfPopulationStandardDeviationTotalAmount(){ + return squareRootOfPopulationStandardDeviationTotalAmountAs(prefix("squareRootOfPopulationStandardDeviationOf",CustomerOrder.TOTAL_AMOUNT_PROPERTY)); + } + + public CustomerOrderRequest squareRootOfPopulationStandardDeviationTotalAmountAs(String retName){ + super.squareRootOfPopulationStandardDeviation(retName, CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + public CustomerOrderRequest sampleVarianceTotalAmount(){ + return sampleVarianceTotalAmountAs(prefix("sampleVarianceOf",CustomerOrder.TOTAL_AMOUNT_PROPERTY)); + } + + public CustomerOrderRequest sampleVarianceTotalAmountAs(String retName){ + super.sampleVariance(retName, CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + public CustomerOrderRequest samplePopulationVarianceTotalAmount(){ + return samplePopulationVarianceTotalAmountAs(prefix("samplePopulationVarianceOf",CustomerOrder.TOTAL_AMOUNT_PROPERTY)); + } + + public CustomerOrderRequest samplePopulationVarianceTotalAmountAs(String retName){ + super.samplePopulationVariance(retName, CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + public CustomerOrderRequest groupByCustomerWithDetails(){ + return groupByCustomerWithDetails(Q.customers().unlimited()); + } + + public CustomerOrderRequest groupByCustomerWithDetails(CustomerRequest subRequest){ + aggregate(CustomerOrder.CUSTOMER_PROPERTY, subRequest); + return this; + } + + public CustomerOrderRequest groupByStatusWithDetails(){ + return groupByStatusWithDetails(Q.orderStatuses().unlimited()); + } + + public CustomerOrderRequest groupByStatusWithDetails(OrderStatusRequest subRequest){ + aggregate(CustomerOrder.STATUS_PROPERTY, subRequest); + return this; + } + + + + + + public CustomerOrderRequest groupByOrderItemsWithDetails(OrderItemRequest subRequest){ + aggregate(CustomerOrder.ORDER_ITEM_LIST_PROPERTY, subRequest); + return this; + } + + public CustomerOrderRequest groupById(){ + groupBy(CustomerOrder.ID_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByIdAs(String retName){ + groupBy(retName, CustomerOrder.ID_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByIdWithFunction(String retName, AggrFunction function){ + groupBy(retName, CustomerOrder.ID_PROPERTY, function); + return this; + } + public CustomerOrderRequest groupByCustomerWith(CustomerRequest subRequest){ + groupBy(CustomerOrder.CUSTOMER_PROPERTY, subRequest); + return this; + } + public CustomerOrderRequest groupByCustomer(){ + groupBy(CustomerOrder.CUSTOMER_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByCustomerAs(String retName){ + groupBy(retName, CustomerOrder.CUSTOMER_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByCustomerWithFunction(String retName, AggrFunction function){ + groupBy(retName, CustomerOrder.CUSTOMER_PROPERTY, function); + return this; + } + public CustomerOrderRequest groupByStatusWith(OrderStatusRequest subRequest){ + groupBy(CustomerOrder.STATUS_PROPERTY, subRequest); + return this; + } + public CustomerOrderRequest groupByStatus(){ + groupBy(CustomerOrder.STATUS_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByStatusAs(String retName){ + groupBy(retName, CustomerOrder.STATUS_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByStatusWithFunction(String retName, AggrFunction function){ + groupBy(retName, CustomerOrder.STATUS_PROPERTY, function); + return this; + } + + public CustomerOrderRequest groupByOrderNumber(){ + groupBy(CustomerOrder.ORDER_NUMBER_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByOrderNumberAs(String retName){ + groupBy(retName, CustomerOrder.ORDER_NUMBER_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByOrderNumberWithFunction(String retName, AggrFunction function){ + groupBy(retName, CustomerOrder.ORDER_NUMBER_PROPERTY, function); + return this; + } + + public CustomerOrderRequest groupByOrderDate(){ + groupBy(CustomerOrder.ORDER_DATE_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByOrderDateAs(String retName){ + groupBy(retName, CustomerOrder.ORDER_DATE_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByOrderDateWithFunction(String retName, AggrFunction function){ + groupBy(retName, CustomerOrder.ORDER_DATE_PROPERTY, function); + return this; + } + + public CustomerOrderRequest groupByTotalAmount(){ + groupBy(CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByTotalAmountAs(String retName){ + groupBy(retName, CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByTotalAmountWithFunction(String retName, AggrFunction function){ + groupBy(retName, CustomerOrder.TOTAL_AMOUNT_PROPERTY, function); + return this; + } + + public CustomerOrderRequest groupByVersion(){ + groupBy(CustomerOrder.VERSION_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByVersionAs(String retName){ + groupBy(retName, CustomerOrder.VERSION_PROPERTY); + return this; + } + + public CustomerOrderRequest groupByVersionWithFunction(String retName, AggrFunction function){ + groupBy(retName, CustomerOrder.VERSION_PROPERTY, function); + return this; + } + + public CustomerOrderRequest withStatusIsNew(){ + filterByStatus(com.example.requesttreeorderservice.Constants.ORDER_STATUS_NEW); + return this; + } + + + public CustomerOrderRequest withStatusIsPaid(){ + filterByStatus(com.example.requesttreeorderservice.Constants.ORDER_STATUS_PAID); + return this; + } + + + public CustomerOrderRequest withStatusIsCancelled(){ + filterByStatus(com.example.requesttreeorderservice.Constants.ORDER_STATUS_CANCELLED); + return this; + } + + + + + public CustomerOrderRequest orderByIdAscending(){ + addOrderByAscending(CustomerOrder.ID_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByIdDescending(){ + addOrderByDescending(CustomerOrder.ID_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByCustomerAscending(){ + addOrderByAscending(CustomerOrder.CUSTOMER_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByCustomerDescending(){ + addOrderByDescending(CustomerOrder.CUSTOMER_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByStatusAscending(){ + addOrderByAscending(CustomerOrder.STATUS_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByStatusDescending(){ + addOrderByDescending(CustomerOrder.STATUS_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByOrderNumberAscending(){ + addOrderByAscending(CustomerOrder.ORDER_NUMBER_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByOrderNumberDescending(){ + addOrderByDescending(CustomerOrder.ORDER_NUMBER_PROPERTY); + return this; + } + public CustomerOrderRequest orderByOrderNumberAscendingUsingGBK(){ + addOrderByAscendingUsingGBK(CustomerOrder.ORDER_NUMBER_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByOrderNumberDescendingUsingGBK(){ + addOrderByDescendingUsingGBK(CustomerOrder.ORDER_NUMBER_PROPERTY); + return this; + } + public CustomerOrderRequest orderByOrderDateAscending(){ + addOrderByAscending(CustomerOrder.ORDER_DATE_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByOrderDateDescending(){ + addOrderByDescending(CustomerOrder.ORDER_DATE_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByTotalAmountAscending(){ + addOrderByAscending(CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByTotalAmountDescending(){ + addOrderByDescending(CustomerOrder.TOTAL_AMOUNT_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByVersionAscending(){ + addOrderByAscending(CustomerOrder.VERSION_PROPERTY); + return this; + } + + public CustomerOrderRequest orderByVersionDescending(){ + addOrderByDescending(CustomerOrder.VERSION_PROPERTY); + return this; + } + + + public CustomerOrderRequest statsFromOrderItemsAs(String name, OrderItemRequest subRequest){ + return statsFromOrderItemsAs(name, subRequest, false); + } + + public CustomerOrderRequest statsFromOrderItemsAs(String name, OrderItemRequest subRequest, boolean singleResult){ + addRelationAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, singleResult, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "statsFromOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest statsFromOrderItems(OrderItemRequest subRequest){ + return statsFromOrderItemsAs(REFINEMENTS, subRequest); + } + public CustomerRequest rollUpToCustomer(){ + CustomerRequest customer = Q.customers().unlimited(); + this.withCustomerMatching(customer) + .groupByCustomerWith(customer); + return customer; + } + + public OrderStatusRequest rollUpToStatus(){ + OrderStatusRequest status = Q.orderStatuses().unlimited(); + this.withStatusMatching(status) + .groupByStatusWith(status); + return status; + } + + + + + + public CustomerOrderRequest countOrderItems(){ + addRelationCount("Count", Q.orderItems().unlimited(), OrderItem.CUSTOMER_ORDER_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest countOrderItemsAs(String name){ + addRelationCount(name, Q.orderItems().unlimited(), OrderItem.CUSTOMER_ORDER_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest countOrderItemsWith(String name, OrderItemRequest subRequest){ + addRelationCount(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countOrderItemsWith", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest minQuantityOfOrderItems(){ + addRelationScalarAggregate("minQuantityOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("min".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "minQuantityOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest minQuantityOfOrderItemsAs(String name){ + return minQuantityOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest minQuantityOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("min".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "minQuantityOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest maxQuantityOfOrderItems(){ + addRelationScalarAggregate("maxQuantityOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("max".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "maxQuantityOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest maxQuantityOfOrderItemsAs(String name){ + return maxQuantityOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest maxQuantityOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("max".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "maxQuantityOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest sumQuantityOfOrderItems(){ + addRelationScalarAggregate("sumQuantityOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("sum".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sumQuantityOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest sumQuantityOfOrderItemsAs(String name){ + return sumQuantityOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest sumQuantityOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("sum".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sumQuantityOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest avgQuantityOfOrderItems(){ + addRelationScalarAggregate("avgQuantityOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("avg".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "avgQuantityOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest avgQuantityOfOrderItemsAs(String name){ + return avgQuantityOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest avgQuantityOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("avg".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "avgQuantityOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest standardDeviationQuantityOfOrderItems(){ + addRelationScalarAggregate("stdDevQuantityOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "standardDeviationQuantityOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest standardDeviationQuantityOfOrderItemsAs(String name){ + return standardDeviationQuantityOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest standardDeviationQuantityOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "standardDeviationQuantityOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest squareRootOfPopulationStandardDeviationQuantityOfOrderItems(){ + addRelationScalarAggregate("stdDevPopQuantityOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "squareRootOfPopulationStandardDeviationQuantityOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest squareRootOfPopulationStandardDeviationQuantityOfOrderItemsAs(String name){ + return squareRootOfPopulationStandardDeviationQuantityOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest squareRootOfPopulationStandardDeviationQuantityOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "squareRootOfPopulationStandardDeviationQuantityOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest sampleVarianceQuantityOfOrderItems(){ + addRelationScalarAggregate("varSampQuantityOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_samp".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sampleVarianceQuantityOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest sampleVarianceQuantityOfOrderItemsAs(String name){ + return sampleVarianceQuantityOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest sampleVarianceQuantityOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_samp".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sampleVarianceQuantityOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest samplePopulationVarianceQuantityOfOrderItems(){ + addRelationScalarAggregate("varPopQuantityOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "samplePopulationVarianceQuantityOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest samplePopulationVarianceQuantityOfOrderItemsAs(String name){ + return samplePopulationVarianceQuantityOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest samplePopulationVarianceQuantityOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.QUANTITY_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "samplePopulationVarianceQuantityOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest minUnitPriceOfOrderItems(){ + addRelationScalarAggregate("minUnitPriceOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("min".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "minUnitPriceOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest minUnitPriceOfOrderItemsAs(String name){ + return minUnitPriceOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest minUnitPriceOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("min".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "minUnitPriceOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest maxUnitPriceOfOrderItems(){ + addRelationScalarAggregate("maxUnitPriceOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("max".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "maxUnitPriceOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest maxUnitPriceOfOrderItemsAs(String name){ + return maxUnitPriceOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest maxUnitPriceOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("max".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "maxUnitPriceOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest sumUnitPriceOfOrderItems(){ + addRelationScalarAggregate("sumUnitPriceOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("sum".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sumUnitPriceOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest sumUnitPriceOfOrderItemsAs(String name){ + return sumUnitPriceOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest sumUnitPriceOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("sum".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sumUnitPriceOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest avgUnitPriceOfOrderItems(){ + addRelationScalarAggregate("avgUnitPriceOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("avg".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "avgUnitPriceOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest avgUnitPriceOfOrderItemsAs(String name){ + return avgUnitPriceOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest avgUnitPriceOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("avg".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "avgUnitPriceOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest standardDeviationUnitPriceOfOrderItems(){ + addRelationScalarAggregate("stdDevUnitPriceOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "standardDeviationUnitPriceOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest standardDeviationUnitPriceOfOrderItemsAs(String name){ + return standardDeviationUnitPriceOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest standardDeviationUnitPriceOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "standardDeviationUnitPriceOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest squareRootOfPopulationStandardDeviationUnitPriceOfOrderItems(){ + addRelationScalarAggregate("stdDevPopUnitPriceOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "squareRootOfPopulationStandardDeviationUnitPriceOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest squareRootOfPopulationStandardDeviationUnitPriceOfOrderItemsAs(String name){ + return squareRootOfPopulationStandardDeviationUnitPriceOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest squareRootOfPopulationStandardDeviationUnitPriceOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "squareRootOfPopulationStandardDeviationUnitPriceOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest sampleVarianceUnitPriceOfOrderItems(){ + addRelationScalarAggregate("varSampUnitPriceOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_samp".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sampleVarianceUnitPriceOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest sampleVarianceUnitPriceOfOrderItemsAs(String name){ + return sampleVarianceUnitPriceOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest sampleVarianceUnitPriceOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_samp".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sampleVarianceUnitPriceOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + public CustomerOrderRequest samplePopulationVarianceUnitPriceOfOrderItems(){ + addRelationScalarAggregate("varPopUnitPriceOfOrderItems", Q.orderItems().unlimited(), + OrderItem.CUSTOMER_ORDER_PROPERTY, OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "samplePopulationVarianceUnitPriceOfOrderItems", null, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest samplePopulationVarianceUnitPriceOfOrderItemsAs(String name){ + return samplePopulationVarianceUnitPriceOfOrderItemsAs(name, Q.orderItems().unlimited()); + } + + public CustomerOrderRequest samplePopulationVarianceUnitPriceOfOrderItemsAs(String name, OrderItemRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderItem.CUSTOMER_ORDER_PROPERTY, + OrderItem.UNIT_PRICE_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "samplePopulationVarianceUnitPriceOfOrderItemsAs", name, CustomerOrder.ORDER_ITEM_LIST_PROPERTY)); + return this; + } + + public CustomerOrderRequest facetByCustomerAs(String facetName, CustomerRequest customer){ + return facetByCustomerAs(facetName, customer, true); + } + + public CustomerOrderRequest facetByCustomerAs(String facetName, CustomerRequest customer, boolean includeAllFacets){ + addFacet(facetName, CustomerOrder.CUSTOMER_PROPERTY, customer, includeAllFacets, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.FACET, "facetByCustomerAs", facetName, CustomerOrder.CUSTOMER_PROPERTY)); + return this; + } + public CustomerOrderRequest facetByStatusAs(String facetName, OrderStatusRequest status){ + return facetByStatusAs(facetName, status, true); + } + + public CustomerOrderRequest facetByStatusAs(String facetName, OrderStatusRequest status, boolean includeAllFacets){ + addFacet(facetName, CustomerOrder.STATUS_PROPERTY, status, includeAllFacets, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.FACET, "facetByStatusAs", facetName, CustomerOrder.STATUS_PROPERTY)); + return this; + } + + + /** + * get topN records + * @param topN records number + */ + public CustomerOrderRequest top(int topN) { + super.top(topN); + return this; + } + + /** Cross-runtime bounded-query alias. */ + public CustomerOrderRequest limit(int limit) { + return top(limit); + } + + /** + * get records from offset(inclusive) to offset+size(exclusive) + * @param offset record offset + * @param size records number + */ + public CustomerOrderRequest offset(int offset, int size) { + super.offset(offset, size); + return this; + } + + /** + * retrieve all records + */ + public CustomerOrderRequest unlimited() { + super.unlimited(); + return this; + } + + /** + * get records of one page + * @param pageNumber page number(1-based) + * @param pageSize page size + */ + public CustomerOrderRequest page(int pageNumber, int pageSize) { + int offset = (pageNumber - 1) * pageSize; + return offset(offset, pageSize); + } + + /** + * get records of one page, default page size is 10 + * @param pageNumber page number(1-based) + */ + public CustomerOrderRequest page(int pageNumber) { + return page(pageNumber, 10); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItem.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItem.java new file mode 100644 index 00000000..763de1a7 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItem.java @@ -0,0 +1,158 @@ + +package com.example.requesttreeorderservice.orderitem; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import io.teaql.core.Audited; +import io.teaql.core.BaseEntity; +import io.teaql.core.EntityStatus; +import io.teaql.core.FieldLayout; +import io.teaql.core.FrameworkInternal; +import io.teaql.core.RemoteInput; +import java.math.BigDecimal; +import java.util.Objects; + +/** + * [TEAQL AI WARNING] + * TeaQL was explicitly designed to PREVENT AI hallucinations and random guessing. + * DO NOT GUESS METHOD NAMES! + * The methods listed below are the ONLY valid ways to interact with this entity. + * If you encounter compilation errors (e.g., method not found), DO NOT guess another method name. + * Read the method signatures in this file before proceeding. + */ +public class OrderItem extends BaseEntity implements RemoteInput { + public static String INTERNAL_TYPE = "OrderItem"; + + // Framework metadata: complete fixed layout, independent of SQL/projection order. + public static final String __TEAQL_FIELD_LAYOUT_REVISION = "c42b4bc2252ab616b0b006ad2560900a40e2ca87d71ad860bab689f0b16b1795"; + public static final java.util.Map __TEAQL_FIXED_FIELD_INDEXES = + java.util.Map.ofEntries(java.util.Map.entry("id", 0), java.util.Map.entry("customer_order", 1), java.util.Map.entry("product_name", 2), java.util.Map.entry("quantity", 3), java.util.Map.entry("unit_price", 4), java.util.Map.entry("version", 5)); + public static final java.util.Map> __TEAQL_FIXED_FIELD_MAPPINGS = + java.util.Map.ofEntries(java.util.Map.entry("id", java.util.List.of("id", "id")), java.util.Map.entry("customer_order", java.util.List.of("customerOrder", "customer_order")), java.util.Map.entry("product_name", java.util.List.of("productName", "product_name")), java.util.Map.entry("quantity", java.util.List.of("quantity", "quantity")), java.util.Map.entry("unit_price", java.util.List.of("unitPrice", "unit_price")), java.util.Map.entry("version", java.util.List.of("version", "version"))); + public static final java.util.Set __TEAQL_FIXED_RELATION_NAMES = java.util.Set.of( +); + public static final FieldLayout __TEAQL_FIELD_LAYOUT = + FieldLayout.installGenerated(FieldLayout.generated( + OrderItem.class, __TEAQL_FIELD_LAYOUT_REVISION, + __TEAQL_FIXED_FIELD_INDEXES, __TEAQL_FIXED_FIELD_MAPPINGS, __TEAQL_FIXED_RELATION_NAMES)); + + + public static final String CUSTOMER_ORDER_PROPERTY = "customerOrder"; + public static final String PRODUCT_NAME_PROPERTY = "productName"; + public static final String QUANTITY_PROPERTY = "quantity"; + public static final String UNIT_PRICE_PROPERTY = "unitPrice"; + private CustomerOrder customerOrder; + + private String productName; + + private Integer quantity; + + private BigDecimal unitPrice; + + + public CustomerOrder getCustomerOrder(){ + return this.customerOrder; + } + + public String getProductName(){ + return this.productName; + } + + public Integer getQuantity(){ + return this.quantity; + } + + public BigDecimal getUnitPrice(){ + return this.unitPrice; + } + + public OrderItem updateCustomerOrder(CustomerOrder customerOrder){ + if(Objects.equals(this.customerOrder, customerOrder)){ + return this; + } + handleUpdate(CUSTOMER_ORDER_PROPERTY, getCustomerOrder(), customerOrder); + this.customerOrder = customerOrder; + return this; + } + + public OrderItem updateProductName(String productName){ + productName = (productName == null ? null : productName.trim()); + if(Objects.equals(this.productName, productName)){ + return this; + } + handleUpdate(PRODUCT_NAME_PROPERTY, getProductName(), productName); + this.productName = productName; + return this; + } + + public OrderItem updateQuantity(Integer quantity){ + if(Objects.equals(this.quantity, quantity)){ + return this; + } + handleUpdate(QUANTITY_PROPERTY, getQuantity(), quantity); + this.quantity = quantity; + return this; + } + + public OrderItem updateUnitPrice(BigDecimal unitPrice){ + if(Objects.equals(this.unitPrice, unitPrice)){ + return this; + } + handleUpdate(UNIT_PRICE_PROPERTY, getUnitPrice(), unitPrice); + this.unitPrice = unitPrice; + return this; + } + + + public static OrderItem refer(Long id){ + OrderItem refer = new OrderItem(); + refer.__internalSet("id", id); + refer.set$status(EntityStatus.REFER); + return refer; + } + @Override + public String typeName(){ + return INTERNAL_TYPE; + } + + public OrderItem comment(String comment){ + this.setComment(comment); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public Audited auditAs(String action) { + return new Audited<>(this, action); + } + + // ===== Framework Internal: generated switch dispatch ===== + @Override + @FrameworkInternal + public void __internalSet(String property, Object value) { + markPropertyLoaded(property); + switch (property) { + case "customerOrder": this.customerOrder = (CustomerOrder) value; break; + + case "productName": this.productName = (value == null ? null : ((String)value).trim()); break; + + case "quantity": this.quantity = (Integer) value; break; + + case "unitPrice": this.unitPrice = (BigDecimal) value; break; + + default: super.__internalSet(property, value); + } + } + + @Override + @FrameworkInternal + public Object __internalGet(String property) { + switch (property) { + case "customerOrder": return this.customerOrder; + case "productName": return this.productName; + case "quantity": return this.quantity; + case "unitPrice": return this.unitPrice; + default: return super.__internalGet(property); + } + } + +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemChecker.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemChecker.java new file mode 100644 index 00000000..7310b890 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemChecker.java @@ -0,0 +1,77 @@ + +package com.example.requesttreeorderservice.orderitem; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.customerorder.CustomerOrderChecker; +import io.teaql.core.UserContext; +import io.teaql.core.checker.Checker; +import io.teaql.core.checker.ObjectLocation; +import java.math.BigDecimal; + +public class OrderItemChecker implements Checker{ + + public String type(){ + return OrderItem.INTERNAL_TYPE; + } + + public void checkAndFix(UserContext _context, OrderItem orderItem, ObjectLocation _parentLocation){ + if(needCheck(_context, orderItem)){ + markAsChecked(_context, orderItem); + doCheck(_context, orderItem, _parentLocation); + } + } + + public void doCheck(UserContext _context, OrderItem orderItem, ObjectLocation _parentLocation){ + if((orderItem == null)){ + return; + } + if(orderItem.newItem()){ + }else if(orderItem.updateItem()){ + if(!orderItem.isPropertyLoaded("customerOrder")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "customer_order"), "Mutation requires a fully loaded entity"); + } + if(!orderItem.isPropertyLoaded("productName")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "product_name"), "Mutation requires a fully loaded entity"); + } + if(!orderItem.isPropertyLoaded("quantity")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "quantity"), "Mutation requires a fully loaded entity"); + } + if(!orderItem.isPropertyLoaded("unitPrice")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "unit_price"), "Mutation requires a fully loaded entity"); + } + + } + checkCustomerOrder(_context, orderItem.getProperty(OrderItem.CUSTOMER_ORDER_PROPERTY), newLocation(_parentLocation, "customer_order")); + checkProductName(_context, orderItem.getProperty(OrderItem.PRODUCT_NAME_PROPERTY), newLocation(_parentLocation, "product_name")); + checkQuantity(_context, orderItem.getProperty(OrderItem.QUANTITY_PROPERTY), newLocation(_parentLocation, "quantity")); + checkUnitPrice(_context, orderItem.getProperty(OrderItem.UNIT_PRICE_PROPERTY), newLocation(_parentLocation, "unit_price")); + } + + public void checkCustomerOrder(UserContext _context, CustomerOrder customerOrder, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, customerOrder); + if((customerOrder == null)){ + return; + } + new CustomerOrderChecker().checkAndFix(_context, customerOrder, _parentLocation); + } + public void checkProductName(UserContext _context, String productName, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, productName); + if((productName == null)){ + return; + } + maxStringCheck(_context, _parentLocation, 100, productName); + + } + public void checkQuantity(UserContext _context, Integer quantity, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, quantity); + if((quantity == null)){ + return; + } + } + public void checkUnitPrice(UserContext _context, BigDecimal unitPrice, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, unitPrice); + if((unitPrice == null)){ + return; + } + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemExpression.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemExpression.java new file mode 100644 index 00000000..f2372bf5 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemExpression.java @@ -0,0 +1,68 @@ + +package com.example.requesttreeorderservice.orderitem; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.customerorder.CustomerOrderExpression; +import io.teaql.core.UserContext; +import io.teaql.core.value.BaseEntityExpression; +import io.teaql.core.value.Expression; +import io.teaql.core.value.ExpressionAdaptor; +import java.math.BigDecimal; +import java.util.function.Function; + +public class OrderItemExpression extends ExpressionAdaptor implements BaseEntityExpression { + public OrderItemExpression(Expression expression){ + super(expression); + } + + public OrderItemExpression(Expression expression, Function function){ + super(expression, function); + } + + public OrderItemExpression updateId(Long id){ + return new OrderItemExpression(this, $it -> {((OrderItem)$it).__internalSet("id", id); return this;}); + } + + public OrderItemExpression save(UserContext userContext){ + return new OrderItemExpression(this, $it -> ((OrderItem)$it).auditAs("Saved by Expression").save(userContext)); + } + + public OrderItemExpression save(String intent, UserContext userContext){ + return new OrderItemExpression(this, $it -> ((OrderItem)$it).auditAs(intent).save(userContext)); + } + + public boolean isNull() { + return resolve() == null; + } + + + public CustomerOrderExpression getCustomerOrder(){ + return new CustomerOrderExpression(loaded("customerOrder", OrderItem::getCustomerOrder)); + } + + public OrderItemExpression updateCustomerOrder(CustomerOrder customerOrder){ + return new OrderItemExpression(this, $it -> ((OrderItem)$it).updateCustomerOrder(customerOrder)); + } + + public Expression getProductName(){ + return loaded("productName", OrderItem::getProductName); + } + public OrderItemExpression updateProductName(String productName){ + return new OrderItemExpression(this, $it -> ((OrderItem)$it).updateProductName(productName)); + } + + public Expression getQuantity(){ + return loaded("quantity", OrderItem::getQuantity); + } + public OrderItemExpression updateQuantity(Integer quantity){ + return new OrderItemExpression(this, $it -> ((OrderItem)$it).updateQuantity(quantity)); + } + + public Expression getUnitPrice(){ + return loaded("unitPrice", OrderItem::getUnitPrice); + } + public OrderItemExpression updateUnitPrice(BigDecimal unitPrice){ + return new OrderItemExpression(this, $it -> ((OrderItem)$it).updateUnitPrice(unitPrice)); + } + +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemListExpression.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemListExpression.java new file mode 100644 index 00000000..da7c16f1 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemListExpression.java @@ -0,0 +1,25 @@ + +package com.example.requesttreeorderservice.orderitem; + +import io.teaql.core.SmartList; +import io.teaql.core.value.Expression; +import io.teaql.core.value.SmartListExpression; +import java.util.function.Function; + +public class OrderItemListExpression extends SmartListExpression { + public OrderItemListExpression(Expression> expression){ + super(expression); + } + + public OrderItemListExpression(Expression expression, Function> function){ + super(expression, function); + } + + public OrderItemExpression first() { + return new OrderItemExpression(super.first()); + } + + public OrderItemExpression get(int index) { + return new OrderItemExpression(super.get(index)); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemRequest.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemRequest.java new file mode 100644 index 00000000..73f962da --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderitem/OrderItemRequest.java @@ -0,0 +1,991 @@ + +package com.example.requesttreeorderservice.orderitem; + +import com.example.requesttreeorderservice.Q; +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.customerorder.CustomerOrderRequest; +import io.teaql.core.AggrFunction; +import io.teaql.core.BaseRequest; +import io.teaql.core.PropertyReference; +import io.teaql.core.SearchCriteria; +import io.teaql.core.SubQuerySearchCriteria; +import io.teaql.core.criteria.Operator; +import io.teaql.core.criteria.TwoOperatorCriteria; +import java.math.BigDecimal; + +public class OrderItemRequest extends BaseRequest { + + /** + * @deprecated AI agents and business code must use the generated Q facade + * instead of constructing request builders directly. + */ + @Deprecated + @SuppressWarnings("unchecked") + public OrderItemRequest(Class returnType){ + this(returnType, () -> (T) new OrderItem()); + } + + protected OrderItemRequest(Class returnType, java.util.function.Supplier factory){ + super(returnType, factory); + selectId(); + selectVersion(); + } + + public OrderItemRequest comment(String comment){ + super.internalComment(comment); + return this; + } + + // purpose() 继承自 BaseRequest,返回 ExecutableRequest(终结方法) + + public OrderItemRequest returnType(Class returnType){ + super.setReturnType(returnType); + return this; + } + + public OrderItemRequest enableAggregationCache(long cacheExpiredMillis){ + super.enableAggregationCache(); + super.aggregateCacheTime(cacheExpiredMillis); + return this; + } + + public OrderItemRequest enableAggregationCache(){ + return enableAggregationCache(0l); + } + + + public OrderItemRequest propagateAggregationCache(long cacheExpiredMillis){ + super.propagateAggregationCache(cacheExpiredMillis); + return this; + } + + /** + * Accept best-effort stateful seek optimization for browsing consecutive pages. + * Do not use this for business processing that must visit every row exactly once. + */ + public OrderItemRequest optimizeForContinuousPageFetch(){ + super.optimizeForContinuousPageFetch(); + return this; + } + + public OrderItemRequest optimizeForContinuousPageFetch(String namespace, int ttlSeconds){ + super.optimizeForContinuousPageFetch(namespace, ttlSeconds); + return this; + } + + public OrderItemRequest optimizePaginationWithIdSet(){ + super.optimizePaginationWithIdSet(); + return this; + } + + public OrderItemRequest optimizePaginationWithIdSet( + String namespace, int ttlSeconds, int maxIds){ + super.optimizePaginationWithIdSet(namespace, ttlSeconds, maxIds); + return this; + } + + public OrderItemRequest topNProbeParentThreshold(int threshold){ + super.topNProbeParentThreshold(threshold); + return this; + } + + public OrderItemRequest appendSearchCriteria(SearchCriteria searchCriteria){ + return (OrderItemRequest)super.appendSearchCriteria(searchCriteria); + } + + public OrderItemRequest filter(String property1, Operator operator, String property2){ + return appendSearchCriteria(new TwoOperatorCriteria(operator, new PropertyReference(property1), new PropertyReference(property2))); + } + + + public OrderItemRequest matchingAnyOf(OrderItemRequest orderItem){ + super.internalMatchAny(orderItem); + return this; + } + + public OrderItemRequest enhanceChildrenIfNeeded(){ + return this; + } + + public OrderItemRequest withDeletedRows(){ + super.withDeletedRows(); + return this; + } + + public OrderItemRequest deletedRowsOnly(){ + super.deletedRowsOnly(); + return this; + } + + public OrderItemRequest selectSelf(){ + super.selectSelf(); + return selectId().selectCustomerOrderIdOnly().selectProductName().selectQuantity().selectUnitPrice().selectVersion(); + } + + public OrderItemRequest selectSelfFields(){ + return selectSelf(); + } + + public OrderItemRequest selectSelfWithoutParent(){ + selectId(); + selectVersion(); + selectProperty(OrderItem.ID_PROPERTY); + selectProperty(OrderItem.CUSTOMER_ORDER_PROPERTY); + selectProperty(OrderItem.PRODUCT_NAME_PROPERTY); + selectProperty(OrderItem.QUANTITY_PROPERTY); + selectProperty(OrderItem.UNIT_PRICE_PROPERTY); + selectProperty(OrderItem.VERSION_PROPERTY); + return this; + } + + public OrderItemRequest selectAll(){ + super.selectAll(); + return selectId().selectCustomerOrder().selectProductName().selectQuantity().selectUnitPrice().selectVersion(); + } + + public OrderItemRequest selectChildren(){ + super.selectAny(); + return selectId().selectCustomerOrder().selectProductName().selectQuantity().selectUnitPrice().selectVersion(); + } + + + public OrderItemRequest selectId(){ + selectProperty(OrderItem.ID_PROPERTY); + return this; + } + + /** + * fill the id with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS id) to fetch id property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public OrderItemRequest unselectId(){ + unselectProperty(OrderItem.ID_PROPERTY); + return this; + } + public OrderItemRequest selectCustomerOrderIdOnly(){ + selectProperty(OrderItem.CUSTOMER_ORDER_PROPERTY); + return this; + } + + public OrderItemRequest selectCustomerOrder(){ + return selectCustomerOrderWith(Q.customerOrders().unlimited().selectSelf()); + } + + public OrderItemRequest selectCustomerOrderWith(CustomerOrderRequest customerOrder){ + selectProperty(OrderItem.CUSTOMER_ORDER_PROPERTY); + enhanceRelation(OrderItem.CUSTOMER_ORDER_PROPERTY, customerOrder, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, "selectCustomerOrderWith", null, OrderItem.CUSTOMER_ORDER_PROPERTY)); + return this; + } + + public OrderItemRequest unselectCustomerOrder(){ + unselectProperty(OrderItem.CUSTOMER_ORDER_PROPERTY); + return this; + } + public OrderItemRequest selectProductName(){ + selectProperty(OrderItem.PRODUCT_NAME_PROPERTY); + return this; + } + + /** + * fill the productName with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS productName) to fetch productName property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public OrderItemRequest unselectProductName(){ + unselectProperty(OrderItem.PRODUCT_NAME_PROPERTY); + return this; + } + public OrderItemRequest selectQuantity(){ + selectProperty(OrderItem.QUANTITY_PROPERTY); + return this; + } + + /** + * fill the quantity with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS quantity) to fetch quantity property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + /** + * fill the quantity with customized aggrFunction, TEAQL uses ({aggrFunction}(quantity) AS quantity to fetch quantity property. + * @param aggrFunction aggrFunction + */ + public OrderItemRequest selectQuantity(AggrFunction aggrFunction){ + selectProperty(OrderItem.QUANTITY_PROPERTY, aggrFunction); + return this; + } + + + public OrderItemRequest unselectQuantity(){ + unselectProperty(OrderItem.QUANTITY_PROPERTY); + return this; + } + public OrderItemRequest selectUnitPrice(){ + selectProperty(OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + + /** + * fill the unitPrice with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS unitPrice) to fetch unitPrice property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + /** + * fill the unitPrice with customized aggrFunction, TEAQL uses ({aggrFunction}(unitPrice) AS unitPrice to fetch unitPrice property. + * @param aggrFunction aggrFunction + */ + public OrderItemRequest selectUnitPrice(AggrFunction aggrFunction){ + selectProperty(OrderItem.UNIT_PRICE_PROPERTY, aggrFunction); + return this; + } + + + public OrderItemRequest unselectUnitPrice(){ + unselectProperty(OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + public OrderItemRequest selectVersion(){ + selectProperty(OrderItem.VERSION_PROPERTY); + return this; + } + + /** + * fill the version with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS version) to fetch version property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public OrderItemRequest unselectVersion(){ + unselectProperty(OrderItem.VERSION_PROPERTY); + return this; + } + + public OrderItemRequest withId(Operator operator, Object... values){ + return appendSearchCriteria(createIdCriteria(operator, values)); + } + + public SearchCriteria createIdCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderItem.ID_PROPERTY, operator, values); + } + + public OrderItemRequest withIdIsNot(Long id){ + return withId(Operator.NOT_EQUAL, id); + } + + public OrderItemRequest withIdIn(Long... id){ + return withId(Operator.IN, (Object[])id); + } + + public OrderItemRequest withIdNotIn(Long... id){ + return withId(Operator.NOT_IN, (Object[])id); + } + public OrderItemRequest withIdIs(Long id){ + return withId(Operator.EQUAL, id); + } + + + + public OrderItemRequest filterByCustomerOrder(CustomerOrder... customerOrder){ + if (customerOrder == null || customerOrder.length == 0) { + throw new IllegalArgumentException("filterByCustomerOrder parameter customerOrder cannot be empty"); + } + return appendSearchCriteria(createCustomerOrderCriteria(Operator.EQUAL, (Object[])customerOrder)); + } + + public OrderItemRequest withCustomerOrder(Operator operator, Object... values){ + return appendSearchCriteria(createCustomerOrderCriteria(operator, values)); + } + + public OrderItemRequest withCustomerOrderIsUnknown(){ + return withCustomerOrder(Operator.IS_NULL); + } + + public OrderItemRequest withCustomerOrderIsKnown(){ + return withCustomerOrder(Operator.IS_NOT_NULL); + } + + public SearchCriteria createCustomerOrderCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderItem.CUSTOMER_ORDER_PROPERTY, operator, values); + } + + public OrderItemRequest filterByCustomerOrder(Long customerOrder){ + if(customerOrder == null){ + return this; + } + return withCustomerOrder(Operator.EQUAL, customerOrder); + } + public OrderItemRequest withCustomerOrderMatching(CustomerOrderRequest customerOrder){ + return appendSearchCriteria(new SubQuerySearchCriteria(OrderItem.CUSTOMER_ORDER_PROPERTY, customerOrder, CustomerOrder.ID_PROPERTY)); + } + + public OrderItemRequest withoutCustomerOrderMatching(CustomerOrderRequest customerOrder){ + return appendSearchCriteria(SearchCriteria.not( + new SubQuerySearchCriteria(OrderItem.CUSTOMER_ORDER_PROPERTY, customerOrder, CustomerOrder.ID_PROPERTY))); + } + + public OrderItemRequest filterByProductName(String... productName){ + if (productName == null || productName.length == 0) { + throw new IllegalArgumentException("filterByProductName parameter productName cannot be empty"); + } + return appendSearchCriteria(createProductNameCriteria(Operator.EQUAL, (Object[])productName)); + } + + public OrderItemRequest withProductName(Operator operator, Object... values){ + return appendSearchCriteria(createProductNameCriteria(operator, values)); + } + + public OrderItemRequest withProductNameIsUnknown(){ + return withProductName(Operator.IS_NULL); + } + + public OrderItemRequest withProductNameIsKnown(){ + return withProductName(Operator.IS_NOT_NULL); + } + + public SearchCriteria createProductNameCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderItem.PRODUCT_NAME_PROPERTY, operator, values); + } + + public OrderItemRequest withProductNameIsNot(String productName){ + return withProductName(Operator.NOT_EQUAL, productName); + } + + public OrderItemRequest withProductNameIn(String... productName){ + return withProductName(Operator.IN, (Object[])productName); + } + + public OrderItemRequest withProductNameNotIn(String... productName){ + return withProductName(Operator.NOT_IN, (Object[])productName); + } + public OrderItemRequest withProductNameGreaterThan(String productName){ + return withProductName(Operator.GREATER_THAN, productName); + } + + public OrderItemRequest withProductNameGreaterThanOrEqualTo(String productName){ + return withProductName(Operator.GREATER_THAN_OR_EQUAL, productName); + } + + public OrderItemRequest withProductNameLessThan(String productName){ + return withProductName(Operator.LESS_THAN, productName); + } + + public OrderItemRequest withProductNameLessThanOrEqualTo(String productName){ + return withProductName(Operator.LESS_THAN_OR_EQUAL, productName); + } + + public OrderItemRequest withProductNameBetween(String startOfProductName, String endOfProductName){ + return withProductName(Operator.BETWEEN, startOfProductName, endOfProductName); + } + public OrderItemRequest withProductNameStartingWith(String productName){ + return withProductName(Operator.BEGIN_WITH, productName); + } + public OrderItemRequest withProductNameContaining(String productName){ + return withProductName(Operator.CONTAIN, productName); + } + + public OrderItemRequest withProductNameNotContaining(String productName){ + return withProductName(Operator.NOT_CONTAIN, productName); + } + + public OrderItemRequest withProductNameNotStartingWith(String productName){ + return withProductName(Operator.NOT_BEGIN_WITH, productName); + } + + public OrderItemRequest withProductNameEndingWith(String productName){ + return withProductName(Operator.END_WITH, productName); + } + + public OrderItemRequest withProductNameNotEndingWith(String productName){ + return withProductName(Operator.NOT_END_WITH, productName); + } + + public OrderItemRequest withProductNameIs(String productName){ + return withProductName(Operator.EQUAL, productName); + } + + public OrderItemRequest withProductNameSoundingLike(String productName){ + return withProductName(Operator.SOUNDS_LIKE, productName); + } + + + + public OrderItemRequest filterByQuantity(Integer... quantity){ + if (quantity == null || quantity.length == 0) { + throw new IllegalArgumentException("filterByQuantity parameter quantity cannot be empty"); + } + return appendSearchCriteria(createQuantityCriteria(Operator.EQUAL, (Object[])quantity)); + } + + public OrderItemRequest withQuantity(Operator operator, Object... values){ + return appendSearchCriteria(createQuantityCriteria(operator, values)); + } + + public OrderItemRequest withQuantityIsUnknown(){ + return withQuantity(Operator.IS_NULL); + } + + public OrderItemRequest withQuantityIsKnown(){ + return withQuantity(Operator.IS_NOT_NULL); + } + + public SearchCriteria createQuantityCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderItem.QUANTITY_PROPERTY, operator, values); + } + + public OrderItemRequest withQuantityIs(Integer quantity){ + return withQuantity(Operator.EQUAL, quantity); + } + + public OrderItemRequest withQuantityIsNot(Integer quantity){ + return withQuantity(Operator.NOT_EQUAL, quantity); + } + + public OrderItemRequest withQuantityIn(Integer... quantity){ + return withQuantity(Operator.IN, (Object[])quantity); + } + + public OrderItemRequest withQuantityNotIn(Integer... quantity){ + return withQuantity(Operator.NOT_IN, (Object[])quantity); + } + public OrderItemRequest withQuantityGreaterThan(Integer quantity){ + return withQuantity(Operator.GREATER_THAN, quantity); + } + + public OrderItemRequest withQuantityGreaterThanOrEqualTo(Integer quantity){ + return withQuantity(Operator.GREATER_THAN_OR_EQUAL, quantity); + } + + public OrderItemRequest withQuantityLessThan(Integer quantity){ + return withQuantity(Operator.LESS_THAN, quantity); + } + + public OrderItemRequest withQuantityLessThanOrEqualTo(Integer quantity){ + return withQuantity(Operator.LESS_THAN_OR_EQUAL, quantity); + } + + public OrderItemRequest withQuantityBetween(Integer startOfQuantity, Integer endOfQuantity){ + return withQuantity(Operator.BETWEEN, startOfQuantity, endOfQuantity); + } + + + + public OrderItemRequest filterByUnitPrice(BigDecimal... unitPrice){ + if (unitPrice == null || unitPrice.length == 0) { + throw new IllegalArgumentException("filterByUnitPrice parameter unitPrice cannot be empty"); + } + return appendSearchCriteria(createUnitPriceCriteria(Operator.EQUAL, (Object[])unitPrice)); + } + + public OrderItemRequest withUnitPrice(Operator operator, Object... values){ + return appendSearchCriteria(createUnitPriceCriteria(operator, values)); + } + + public OrderItemRequest withUnitPriceIsUnknown(){ + return withUnitPrice(Operator.IS_NULL); + } + + public OrderItemRequest withUnitPriceIsKnown(){ + return withUnitPrice(Operator.IS_NOT_NULL); + } + + public SearchCriteria createUnitPriceCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderItem.UNIT_PRICE_PROPERTY, operator, values); + } + + public OrderItemRequest withUnitPriceIs(BigDecimal unitPrice){ + return withUnitPrice(Operator.EQUAL, unitPrice); + } + + public OrderItemRequest withUnitPriceIsNot(BigDecimal unitPrice){ + return withUnitPrice(Operator.NOT_EQUAL, unitPrice); + } + + public OrderItemRequest withUnitPriceIn(BigDecimal... unitPrice){ + return withUnitPrice(Operator.IN, (Object[])unitPrice); + } + + public OrderItemRequest withUnitPriceNotIn(BigDecimal... unitPrice){ + return withUnitPrice(Operator.NOT_IN, (Object[])unitPrice); + } + public OrderItemRequest withUnitPriceGreaterThan(BigDecimal unitPrice){ + return withUnitPrice(Operator.GREATER_THAN, unitPrice); + } + + public OrderItemRequest withUnitPriceGreaterThanOrEqualTo(BigDecimal unitPrice){ + return withUnitPrice(Operator.GREATER_THAN_OR_EQUAL, unitPrice); + } + + public OrderItemRequest withUnitPriceLessThan(BigDecimal unitPrice){ + return withUnitPrice(Operator.LESS_THAN, unitPrice); + } + + public OrderItemRequest withUnitPriceLessThanOrEqualTo(BigDecimal unitPrice){ + return withUnitPrice(Operator.LESS_THAN_OR_EQUAL, unitPrice); + } + + public OrderItemRequest withUnitPriceBetween(BigDecimal startOfUnitPrice, BigDecimal endOfUnitPrice){ + return withUnitPrice(Operator.BETWEEN, startOfUnitPrice, endOfUnitPrice); + } + + + + public OrderItemRequest filterByVersion(Long... version){ + if (version == null || version.length == 0) { + throw new IllegalArgumentException("filterByVersion parameter version cannot be empty"); + } + return appendSearchCriteria(createVersionCriteria(Operator.EQUAL, (Object[])version)); + } + + public OrderItemRequest withVersion(Operator operator, Object... values){ + return appendSearchCriteria(createVersionCriteria(operator, values)); + } + + public OrderItemRequest withVersionIsUnknown(){ + return withVersion(Operator.IS_NULL); + } + + public OrderItemRequest withVersionIsKnown(){ + return withVersion(Operator.IS_NOT_NULL); + } + + public SearchCriteria createVersionCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderItem.VERSION_PROPERTY, operator, values); + } + + public OrderItemRequest withVersionIs(Long version){ + return withVersion(Operator.EQUAL, version); + } + + public OrderItemRequest withVersionIsNot(Long version){ + return withVersion(Operator.NOT_EQUAL, version); + } + + public OrderItemRequest withVersionIn(Long... version){ + return withVersion(Operator.IN, (Object[])version); + } + + public OrderItemRequest withVersionNotIn(Long... version){ + return withVersion(Operator.NOT_IN, (Object[])version); + } + public OrderItemRequest withVersionGreaterThan(Long version){ + return withVersion(Operator.GREATER_THAN, version); + } + + public OrderItemRequest withVersionGreaterThanOrEqualTo(Long version){ + return withVersion(Operator.GREATER_THAN_OR_EQUAL, version); + } + + public OrderItemRequest withVersionLessThan(Long version){ + return withVersion(Operator.LESS_THAN, version); + } + + public OrderItemRequest withVersionLessThanOrEqualTo(Long version){ + return withVersion(Operator.LESS_THAN_OR_EQUAL, version); + } + + public OrderItemRequest withVersionBetween(Long startOfVersion, Long endOfVersion){ + return withVersion(Operator.BETWEEN, startOfVersion, endOfVersion); + } + + + + public OrderItemRequest count(){ + super.count(); + setPageCountOperation(io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.NAMED_COUNT, "count", null, null)); + return this; + } + public OrderItemRequest countAs(String retName){ + super.count(retName); + setPageCountOperation(io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.NAMED_COUNT, "countAs", retName, null)); + return this; + } + public OrderItemRequest minQuantity(){ + return minQuantityAs(prefix("minOf",OrderItem.QUANTITY_PROPERTY)); + } + + public OrderItemRequest minQuantityAs(String retName){ + super.min(retName, OrderItem.QUANTITY_PROPERTY); + return this; + } + public OrderItemRequest maxQuantity(){ + return maxQuantityAs(prefix("maxOf",OrderItem.QUANTITY_PROPERTY)); + } + + public OrderItemRequest maxQuantityAs(String retName){ + super.max(retName, OrderItem.QUANTITY_PROPERTY); + return this; + } + public OrderItemRequest sumQuantity(){ + return sumQuantityAs(prefix("sumOf",OrderItem.QUANTITY_PROPERTY)); + } + + public OrderItemRequest sumQuantityAs(String retName){ + super.sum(retName, OrderItem.QUANTITY_PROPERTY); + return this; + } + public OrderItemRequest avgQuantity(){ + return avgQuantityAs(prefix("avgOf",OrderItem.QUANTITY_PROPERTY)); + } + + public OrderItemRequest avgQuantityAs(String retName){ + super.avg(retName, OrderItem.QUANTITY_PROPERTY); + return this; + } + public OrderItemRequest standardDeviationQuantity(){ + return standardDeviationQuantityAs(prefix("standardDeviationOf",OrderItem.QUANTITY_PROPERTY)); + } + + public OrderItemRequest standardDeviationQuantityAs(String retName){ + super.standardDeviation(retName, OrderItem.QUANTITY_PROPERTY); + return this; + } + public OrderItemRequest squareRootOfPopulationStandardDeviationQuantity(){ + return squareRootOfPopulationStandardDeviationQuantityAs(prefix("squareRootOfPopulationStandardDeviationOf",OrderItem.QUANTITY_PROPERTY)); + } + + public OrderItemRequest squareRootOfPopulationStandardDeviationQuantityAs(String retName){ + super.squareRootOfPopulationStandardDeviation(retName, OrderItem.QUANTITY_PROPERTY); + return this; + } + public OrderItemRequest sampleVarianceQuantity(){ + return sampleVarianceQuantityAs(prefix("sampleVarianceOf",OrderItem.QUANTITY_PROPERTY)); + } + + public OrderItemRequest sampleVarianceQuantityAs(String retName){ + super.sampleVariance(retName, OrderItem.QUANTITY_PROPERTY); + return this; + } + public OrderItemRequest samplePopulationVarianceQuantity(){ + return samplePopulationVarianceQuantityAs(prefix("samplePopulationVarianceOf",OrderItem.QUANTITY_PROPERTY)); + } + + public OrderItemRequest samplePopulationVarianceQuantityAs(String retName){ + super.samplePopulationVariance(retName, OrderItem.QUANTITY_PROPERTY); + return this; + } + public OrderItemRequest minUnitPrice(){ + return minUnitPriceAs(prefix("minOf",OrderItem.UNIT_PRICE_PROPERTY)); + } + + public OrderItemRequest minUnitPriceAs(String retName){ + super.min(retName, OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + public OrderItemRequest maxUnitPrice(){ + return maxUnitPriceAs(prefix("maxOf",OrderItem.UNIT_PRICE_PROPERTY)); + } + + public OrderItemRequest maxUnitPriceAs(String retName){ + super.max(retName, OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + public OrderItemRequest sumUnitPrice(){ + return sumUnitPriceAs(prefix("sumOf",OrderItem.UNIT_PRICE_PROPERTY)); + } + + public OrderItemRequest sumUnitPriceAs(String retName){ + super.sum(retName, OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + public OrderItemRequest avgUnitPrice(){ + return avgUnitPriceAs(prefix("avgOf",OrderItem.UNIT_PRICE_PROPERTY)); + } + + public OrderItemRequest avgUnitPriceAs(String retName){ + super.avg(retName, OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + public OrderItemRequest standardDeviationUnitPrice(){ + return standardDeviationUnitPriceAs(prefix("standardDeviationOf",OrderItem.UNIT_PRICE_PROPERTY)); + } + + public OrderItemRequest standardDeviationUnitPriceAs(String retName){ + super.standardDeviation(retName, OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + public OrderItemRequest squareRootOfPopulationStandardDeviationUnitPrice(){ + return squareRootOfPopulationStandardDeviationUnitPriceAs(prefix("squareRootOfPopulationStandardDeviationOf",OrderItem.UNIT_PRICE_PROPERTY)); + } + + public OrderItemRequest squareRootOfPopulationStandardDeviationUnitPriceAs(String retName){ + super.squareRootOfPopulationStandardDeviation(retName, OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + public OrderItemRequest sampleVarianceUnitPrice(){ + return sampleVarianceUnitPriceAs(prefix("sampleVarianceOf",OrderItem.UNIT_PRICE_PROPERTY)); + } + + public OrderItemRequest sampleVarianceUnitPriceAs(String retName){ + super.sampleVariance(retName, OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + public OrderItemRequest samplePopulationVarianceUnitPrice(){ + return samplePopulationVarianceUnitPriceAs(prefix("samplePopulationVarianceOf",OrderItem.UNIT_PRICE_PROPERTY)); + } + + public OrderItemRequest samplePopulationVarianceUnitPriceAs(String retName){ + super.samplePopulationVariance(retName, OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + public OrderItemRequest groupByCustomerOrderWithDetails(){ + return groupByCustomerOrderWithDetails(Q.customerOrders().unlimited()); + } + + public OrderItemRequest groupByCustomerOrderWithDetails(CustomerOrderRequest subRequest){ + aggregate(OrderItem.CUSTOMER_ORDER_PROPERTY, subRequest); + return this; + } + + + + + + + public OrderItemRequest groupById(){ + groupBy(OrderItem.ID_PROPERTY); + return this; + } + + public OrderItemRequest groupByIdAs(String retName){ + groupBy(retName, OrderItem.ID_PROPERTY); + return this; + } + + public OrderItemRequest groupByIdWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderItem.ID_PROPERTY, function); + return this; + } + public OrderItemRequest groupByCustomerOrderWith(CustomerOrderRequest subRequest){ + groupBy(OrderItem.CUSTOMER_ORDER_PROPERTY, subRequest); + return this; + } + public OrderItemRequest groupByCustomerOrder(){ + groupBy(OrderItem.CUSTOMER_ORDER_PROPERTY); + return this; + } + + public OrderItemRequest groupByCustomerOrderAs(String retName){ + groupBy(retName, OrderItem.CUSTOMER_ORDER_PROPERTY); + return this; + } + + public OrderItemRequest groupByCustomerOrderWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderItem.CUSTOMER_ORDER_PROPERTY, function); + return this; + } + + public OrderItemRequest groupByProductName(){ + groupBy(OrderItem.PRODUCT_NAME_PROPERTY); + return this; + } + + public OrderItemRequest groupByProductNameAs(String retName){ + groupBy(retName, OrderItem.PRODUCT_NAME_PROPERTY); + return this; + } + + public OrderItemRequest groupByProductNameWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderItem.PRODUCT_NAME_PROPERTY, function); + return this; + } + + public OrderItemRequest groupByQuantity(){ + groupBy(OrderItem.QUANTITY_PROPERTY); + return this; + } + + public OrderItemRequest groupByQuantityAs(String retName){ + groupBy(retName, OrderItem.QUANTITY_PROPERTY); + return this; + } + + public OrderItemRequest groupByQuantityWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderItem.QUANTITY_PROPERTY, function); + return this; + } + + public OrderItemRequest groupByUnitPrice(){ + groupBy(OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + + public OrderItemRequest groupByUnitPriceAs(String retName){ + groupBy(retName, OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + + public OrderItemRequest groupByUnitPriceWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderItem.UNIT_PRICE_PROPERTY, function); + return this; + } + + public OrderItemRequest groupByVersion(){ + groupBy(OrderItem.VERSION_PROPERTY); + return this; + } + + public OrderItemRequest groupByVersionAs(String retName){ + groupBy(retName, OrderItem.VERSION_PROPERTY); + return this; + } + + public OrderItemRequest groupByVersionWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderItem.VERSION_PROPERTY, function); + return this; + } + + + + public OrderItemRequest orderByIdAscending(){ + addOrderByAscending(OrderItem.ID_PROPERTY); + return this; + } + + public OrderItemRequest orderByIdDescending(){ + addOrderByDescending(OrderItem.ID_PROPERTY); + return this; + } + + public OrderItemRequest orderByCustomerOrderAscending(){ + addOrderByAscending(OrderItem.CUSTOMER_ORDER_PROPERTY); + return this; + } + + public OrderItemRequest orderByCustomerOrderDescending(){ + addOrderByDescending(OrderItem.CUSTOMER_ORDER_PROPERTY); + return this; + } + + public OrderItemRequest orderByProductNameAscending(){ + addOrderByAscending(OrderItem.PRODUCT_NAME_PROPERTY); + return this; + } + + public OrderItemRequest orderByProductNameDescending(){ + addOrderByDescending(OrderItem.PRODUCT_NAME_PROPERTY); + return this; + } + public OrderItemRequest orderByProductNameAscendingUsingGBK(){ + addOrderByAscendingUsingGBK(OrderItem.PRODUCT_NAME_PROPERTY); + return this; + } + + public OrderItemRequest orderByProductNameDescendingUsingGBK(){ + addOrderByDescendingUsingGBK(OrderItem.PRODUCT_NAME_PROPERTY); + return this; + } + public OrderItemRequest orderByQuantityAscending(){ + addOrderByAscending(OrderItem.QUANTITY_PROPERTY); + return this; + } + + public OrderItemRequest orderByQuantityDescending(){ + addOrderByDescending(OrderItem.QUANTITY_PROPERTY); + return this; + } + + public OrderItemRequest orderByUnitPriceAscending(){ + addOrderByAscending(OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + + public OrderItemRequest orderByUnitPriceDescending(){ + addOrderByDescending(OrderItem.UNIT_PRICE_PROPERTY); + return this; + } + + public OrderItemRequest orderByVersionAscending(){ + addOrderByAscending(OrderItem.VERSION_PROPERTY); + return this; + } + + public OrderItemRequest orderByVersionDescending(){ + addOrderByDescending(OrderItem.VERSION_PROPERTY); + return this; + } + + + public CustomerOrderRequest rollUpToCustomerOrder(){ + CustomerOrderRequest customerOrder = Q.customerOrders().unlimited(); + this.withCustomerOrderMatching(customerOrder) + .groupByCustomerOrderWith(customerOrder); + return customerOrder; + } + + + + + + + public OrderItemRequest facetByCustomerOrderAs(String facetName, CustomerOrderRequest customerOrder){ + return facetByCustomerOrderAs(facetName, customerOrder, true); + } + + public OrderItemRequest facetByCustomerOrderAs(String facetName, CustomerOrderRequest customerOrder, boolean includeAllFacets){ + addFacet(facetName, OrderItem.CUSTOMER_ORDER_PROPERTY, customerOrder, includeAllFacets, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.FACET, "facetByCustomerOrderAs", facetName, OrderItem.CUSTOMER_ORDER_PROPERTY)); + return this; + } + + + /** + * get topN records + * @param topN records number + */ + public OrderItemRequest top(int topN) { + super.top(topN); + return this; + } + + /** Cross-runtime bounded-query alias. */ + public OrderItemRequest limit(int limit) { + return top(limit); + } + + /** + * get records from offset(inclusive) to offset+size(exclusive) + * @param offset record offset + * @param size records number + */ + public OrderItemRequest offset(int offset, int size) { + super.offset(offset, size); + return this; + } + + /** + * retrieve all records + */ + public OrderItemRequest unlimited() { + super.unlimited(); + return this; + } + + /** + * get records of one page + * @param pageNumber page number(1-based) + * @param pageSize page size + */ + public OrderItemRequest page(int pageNumber, int pageSize) { + int offset = (pageNumber - 1) * pageSize; + return offset(offset, pageSize); + } + + /** + * get records of one page, default page size is 10 + * @param pageNumber page number(1-based) + */ + public OrderItemRequest page(int pageNumber) { + return page(pageNumber, 10); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatus.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatus.java new file mode 100644 index 00000000..bda0d7d4 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatus.java @@ -0,0 +1,201 @@ + +package com.example.requesttreeorderservice.orderstatus; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.platform.Platform; +import io.teaql.core.Audited; +import io.teaql.core.BaseEntity; +import io.teaql.core.EntityStatus; +import io.teaql.core.FieldLayout; +import io.teaql.core.FrameworkInternal; +import io.teaql.core.RemoteInput; +import io.teaql.core.SmartList; +import java.math.BigDecimal; +import java.util.Objects; + +/** + * [TEAQL AI WARNING] + * TeaQL was explicitly designed to PREVENT AI hallucinations and random guessing. + * DO NOT GUESS METHOD NAMES! + * The methods listed below are the ONLY valid ways to interact with this entity. + * If you encounter compilation errors (e.g., method not found), DO NOT guess another method name. + * Read the method signatures in this file before proceeding. + */ +public class OrderStatus extends BaseEntity implements RemoteInput { + public static String INTERNAL_TYPE = "OrderStatus"; + + // Framework metadata: complete fixed layout, independent of SQL/projection order. + public static final String __TEAQL_FIELD_LAYOUT_REVISION = "81304ab2e3fd61964bc163a850a2a321e1c0bfe204f85f36aedc4e1b5d8cebcd"; + public static final java.util.Map __TEAQL_FIXED_FIELD_INDEXES = + java.util.Map.ofEntries(java.util.Map.entry("platform", 0), java.util.Map.entry("id", 1), java.util.Map.entry("name", 2), java.util.Map.entry("code", 3), java.util.Map.entry("color", 4), java.util.Map.entry("display_order", 5), java.util.Map.entry("version", 6)); + public static final java.util.Map> __TEAQL_FIXED_FIELD_MAPPINGS = + java.util.Map.ofEntries(java.util.Map.entry("platform", java.util.List.of("platform", "platform")), java.util.Map.entry("id", java.util.List.of("id", "id")), java.util.Map.entry("name", java.util.List.of("name", "name")), java.util.Map.entry("code", java.util.List.of("code", "code")), java.util.Map.entry("color", java.util.List.of("color", "color")), java.util.Map.entry("display_order", java.util.List.of("displayOrder", "display_order")), java.util.Map.entry("version", java.util.List.of("version", "version"))); + public static final java.util.Set __TEAQL_FIXED_RELATION_NAMES = java.util.Set.of( + "customerOrderList"); + public static final FieldLayout __TEAQL_FIELD_LAYOUT = + FieldLayout.installGenerated(FieldLayout.generated( + OrderStatus.class, __TEAQL_FIELD_LAYOUT_REVISION, + __TEAQL_FIXED_FIELD_INDEXES, __TEAQL_FIXED_FIELD_MAPPINGS, __TEAQL_FIXED_RELATION_NAMES)); + + + public static final String PLATFORM_PROPERTY = "platform"; + public static final String NAME_PROPERTY = "name"; + public static final String CODE_PROPERTY = "code"; + public static final String COLOR_PROPERTY = "color"; + public static final String DISPLAY_ORDER_PROPERTY = "displayOrder"; + public static final String CUSTOMER_ORDER_LIST_PROPERTY = "customerOrderList"; + private Platform platform; + + private String name; + + private String code; + + private String color; + + private BigDecimal displayOrder; + + private SmartList customerOrderList; + + public Platform getPlatform(){ + return this.platform; + } + + public String getName(){ + return this.name; + } + + public String getCode(){ + return this.code; + } + + public String getColor(){ + return this.color; + } + + public BigDecimal getDisplayOrder(){ + return this.displayOrder; + } + + public SmartList getCustomerOrderList(){ + return this.customerOrderList; + } + public OrderStatus updatePlatform(Platform platform){ + if(Objects.equals(this.platform, platform)){ + return this; + } + handleUpdate(PLATFORM_PROPERTY, getPlatform(), platform); + this.platform = platform; + return this; + } + + public OrderStatus updateName(String name){ + name = (name == null ? null : name.trim()); + if(Objects.equals(this.name, name)){ + return this; + } + handleUpdate(NAME_PROPERTY, getName(), name); + this.name = name; + return this; + } + + public OrderStatus updateCode(String code){ + code = (code == null ? null : code.trim()); + if(Objects.equals(this.code, code)){ + return this; + } + handleUpdate(CODE_PROPERTY, getCode(), code); + this.code = code; + return this; + } + + public OrderStatus updateColor(String color){ + color = (color == null ? null : color.trim()); + if(Objects.equals(this.color, color)){ + return this; + } + handleUpdate(COLOR_PROPERTY, getColor(), color); + this.color = color; + return this; + } + + public OrderStatus updateDisplayOrder(BigDecimal displayOrder){ + if(Objects.equals(this.displayOrder, displayOrder)){ + return this; + } + handleUpdate(DISPLAY_ORDER_PROPERTY, getDisplayOrder(), displayOrder); + this.displayOrder = displayOrder; + return this; + } + + public OrderStatus addCustomerOrder(CustomerOrder customerOrder){ + if (customerOrder == null){ + return this; + } + + if(null == this.customerOrderList){ + this.customerOrderList = new SmartList<>(); + } + + this.customerOrderList.add(customerOrder); + customerOrder.updateProperty(CustomerOrder.STATUS_PROPERTY, this); + return this; + } + + public static OrderStatus refer(Long id){ + OrderStatus refer = new OrderStatus(); + refer.__internalSet("id", id); + refer.set$status(EntityStatus.REFER); + return refer; + } + @Override + public String typeName(){ + return INTERNAL_TYPE; + } + + public OrderStatus comment(String comment){ + this.setComment(comment); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public Audited auditAs(String action) { + return new Audited<>(this, action); + } + + // ===== Framework Internal: generated switch dispatch ===== + @Override + @FrameworkInternal + public void __internalSet(String property, Object value) { + markPropertyLoaded(property); + switch (property) { + case "platform": this.platform = (Platform) value; break; + + case "name": this.name = (value == null ? null : ((String)value).trim()); break; + + case "code": this.code = (value == null ? null : ((String)value).trim()); break; + + case "color": this.color = (value == null ? null : ((String)value).trim()); break; + + case "displayOrder": this.displayOrder = (BigDecimal) value; break; + + case "customerOrderList": this.customerOrderList = (SmartList) value; break; + default: super.__internalSet(property, value); + } + } + + @Override + @FrameworkInternal + public Object __internalGet(String property) { + switch (property) { + case "platform": return this.platform; + case "name": return this.name; + case "code": return this.code; + case "color": return this.color; + case "displayOrder": return this.displayOrder; + case "customerOrderList": return this.customerOrderList; + default: return super.__internalGet(property); + } + } + +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusChecker.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusChecker.java new file mode 100644 index 00000000..f7ad7ece --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusChecker.java @@ -0,0 +1,97 @@ + +package com.example.requesttreeorderservice.orderstatus; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.customerorder.CustomerOrderChecker; +import com.example.requesttreeorderservice.platform.Platform; +import com.example.requesttreeorderservice.platform.PlatformChecker; +import io.teaql.core.UserContext; +import io.teaql.core.checker.Checker; +import io.teaql.core.checker.ObjectLocation; +import java.math.BigDecimal; + +public class OrderStatusChecker implements Checker{ + + public String type(){ + return OrderStatus.INTERNAL_TYPE; + } + + public void checkAndFix(UserContext _context, OrderStatus orderStatus, ObjectLocation _parentLocation){ + if(needCheck(_context, orderStatus)){ + markAsChecked(_context, orderStatus); + doCheck(_context, orderStatus, _parentLocation); + } + } + + public void doCheck(UserContext _context, OrderStatus orderStatus, ObjectLocation _parentLocation){ + if((orderStatus == null)){ + return; + } + if(orderStatus.newItem()){ + }else if(orderStatus.updateItem()){ + if(!orderStatus.isPropertyLoaded("platform")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "platform"), "Mutation requires a fully loaded entity"); + } + if(!orderStatus.isPropertyLoaded("name")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "name"), "Mutation requires a fully loaded entity"); + } + if(!orderStatus.isPropertyLoaded("code")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "code"), "Mutation requires a fully loaded entity"); + } + if(!orderStatus.isPropertyLoaded("color")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "color"), "Mutation requires a fully loaded entity"); + } + if(!orderStatus.isPropertyLoaded("displayOrder")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "display_order"), "Mutation requires a fully loaded entity"); + } + + } + checkPlatform(_context, orderStatus.getProperty(OrderStatus.PLATFORM_PROPERTY), newLocation(_parentLocation, "platform")); + checkName(_context, orderStatus.getProperty(OrderStatus.NAME_PROPERTY), newLocation(_parentLocation, "name")); + checkCode(_context, orderStatus.getProperty(OrderStatus.CODE_PROPERTY), newLocation(_parentLocation, "code")); + checkColor(_context, orderStatus.getProperty(OrderStatus.COLOR_PROPERTY), newLocation(_parentLocation, "color")); + checkDisplayOrder(_context, orderStatus.getProperty(OrderStatus.DISPLAY_ORDER_PROPERTY), newLocation(_parentLocation, "display_order")); + for(int i = 0; orderStatus.getCustomerOrderList() != null && i < orderStatus.getCustomerOrderList().size(); i++){ + CustomerOrder customerOrder = orderStatus.getCustomerOrderList().get(i); + new CustomerOrderChecker().checkAndFix(_context, customerOrder, newLocation(_parentLocation, "customer_order_list", i)); + } + } + + public void checkPlatform(UserContext _context, Platform platform, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, platform); + if((platform == null)){ + return; + } + new PlatformChecker().checkAndFix(_context, platform, _parentLocation); + } + public void checkName(UserContext _context, String name, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, name); + if((name == null)){ + return; + } + maxStringCheck(_context, _parentLocation, 100, name); + + } + public void checkCode(UserContext _context, String code, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, code); + if((code == null)){ + return; + } + maxStringCheck(_context, _parentLocation, 100, code); + + } + public void checkColor(UserContext _context, String color, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, color); + if((color == null)){ + return; + } + maxStringCheck(_context, _parentLocation, 100, color); + + } + public void checkDisplayOrder(UserContext _context, BigDecimal displayOrder, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, displayOrder); + if((displayOrder == null)){ + return; + } + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusExpression.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusExpression.java new file mode 100644 index 00000000..a5e5fe33 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusExpression.java @@ -0,0 +1,83 @@ + +package com.example.requesttreeorderservice.orderstatus; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.customerorder.CustomerOrderListExpression; +import com.example.requesttreeorderservice.platform.Platform; +import com.example.requesttreeorderservice.platform.PlatformExpression; +import io.teaql.core.UserContext; +import io.teaql.core.value.BaseEntityExpression; +import io.teaql.core.value.Expression; +import io.teaql.core.value.ExpressionAdaptor; +import java.math.BigDecimal; +import java.util.function.Function; + +public class OrderStatusExpression extends ExpressionAdaptor implements BaseEntityExpression { + public OrderStatusExpression(Expression expression){ + super(expression); + } + + public OrderStatusExpression(Expression expression, Function function){ + super(expression, function); + } + + public OrderStatusExpression updateId(Long id){ + return new OrderStatusExpression(this, $it -> {((OrderStatus)$it).__internalSet("id", id); return this;}); + } + + public OrderStatusExpression save(UserContext userContext){ + return new OrderStatusExpression(this, $it -> ((OrderStatus)$it).auditAs("Saved by Expression").save(userContext)); + } + + public OrderStatusExpression save(String intent, UserContext userContext){ + return new OrderStatusExpression(this, $it -> ((OrderStatus)$it).auditAs(intent).save(userContext)); + } + + public boolean isNull() { + return resolve() == null; + } + + + public PlatformExpression getPlatform(){ + return new PlatformExpression(loaded("platform", OrderStatus::getPlatform)); + } + + public OrderStatusExpression updatePlatform(Platform platform){ + return new OrderStatusExpression(this, $it -> ((OrderStatus)$it).updatePlatform(platform)); + } + + public Expression getName(){ + return loaded("name", OrderStatus::getName); + } + public OrderStatusExpression updateName(String name){ + return new OrderStatusExpression(this, $it -> ((OrderStatus)$it).updateName(name)); + } + + public Expression getCode(){ + return loaded("code", OrderStatus::getCode); + } + public OrderStatusExpression updateCode(String code){ + return new OrderStatusExpression(this, $it -> ((OrderStatus)$it).updateCode(code)); + } + + public Expression getColor(){ + return loaded("color", OrderStatus::getColor); + } + public OrderStatusExpression updateColor(String color){ + return new OrderStatusExpression(this, $it -> ((OrderStatus)$it).updateColor(color)); + } + + public Expression getDisplayOrder(){ + return loaded("displayOrder", OrderStatus::getDisplayOrder); + } + public OrderStatusExpression updateDisplayOrder(BigDecimal displayOrder){ + return new OrderStatusExpression(this, $it -> ((OrderStatus)$it).updateDisplayOrder(displayOrder)); + } + + public CustomerOrderListExpression getCustomerOrderList(){ + return new CustomerOrderListExpression(loaded("customerOrderList", OrderStatus::getCustomerOrderList)); + } + public OrderStatusExpression addCustomerOrder(CustomerOrder customerOrder){ + return new OrderStatusExpression(this, $it -> ((OrderStatus)$it).addCustomerOrder(customerOrder)); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusListExpression.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusListExpression.java new file mode 100644 index 00000000..483767e2 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusListExpression.java @@ -0,0 +1,25 @@ + +package com.example.requesttreeorderservice.orderstatus; + +import io.teaql.core.SmartList; +import io.teaql.core.value.Expression; +import io.teaql.core.value.SmartListExpression; +import java.util.function.Function; + +public class OrderStatusListExpression extends SmartListExpression { + public OrderStatusListExpression(Expression> expression){ + super(expression); + } + + public OrderStatusListExpression(Expression expression, Function> function){ + super(expression, function); + } + + public OrderStatusExpression first() { + return new OrderStatusExpression(super.first()); + } + + public OrderStatusExpression get(int index) { + return new OrderStatusExpression(super.get(index)); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusRequest.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusRequest.java new file mode 100644 index 00000000..d4713aac --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/orderstatus/OrderStatusRequest.java @@ -0,0 +1,1308 @@ + +package com.example.requesttreeorderservice.orderstatus; + +import com.example.requesttreeorderservice.Q; +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.customerorder.CustomerOrderRequest; +import com.example.requesttreeorderservice.platform.Platform; +import com.example.requesttreeorderservice.platform.PlatformRequest; +import io.teaql.core.AggrFunction; +import io.teaql.core.BaseRequest; +import io.teaql.core.PropertyReference; +import io.teaql.core.SearchCriteria; +import io.teaql.core.SubQuerySearchCriteria; +import io.teaql.core.criteria.Operator; +import io.teaql.core.criteria.TwoOperatorCriteria; +import java.math.BigDecimal; + +public class OrderStatusRequest extends BaseRequest { + + /** + * @deprecated AI agents and business code must use the generated Q facade + * instead of constructing request builders directly. + */ + @Deprecated + @SuppressWarnings("unchecked") + public OrderStatusRequest(Class returnType){ + this(returnType, () -> (T) new OrderStatus()); + } + + protected OrderStatusRequest(Class returnType, java.util.function.Supplier factory){ + super(returnType, factory); + selectId(); + selectVersion(); + } + + public OrderStatusRequest comment(String comment){ + super.internalComment(comment); + return this; + } + + // purpose() 继承自 BaseRequest,返回 ExecutableRequest(终结方法) + + public OrderStatusRequest returnType(Class returnType){ + super.setReturnType(returnType); + return this; + } + + public OrderStatusRequest enableAggregationCache(long cacheExpiredMillis){ + super.enableAggregationCache(); + super.aggregateCacheTime(cacheExpiredMillis); + return this; + } + + public OrderStatusRequest enableAggregationCache(){ + return enableAggregationCache(0l); + } + + + public OrderStatusRequest propagateAggregationCache(long cacheExpiredMillis){ + super.propagateAggregationCache(cacheExpiredMillis); + return this; + } + + /** + * Accept best-effort stateful seek optimization for browsing consecutive pages. + * Do not use this for business processing that must visit every row exactly once. + */ + public OrderStatusRequest optimizeForContinuousPageFetch(){ + super.optimizeForContinuousPageFetch(); + return this; + } + + public OrderStatusRequest optimizeForContinuousPageFetch(String namespace, int ttlSeconds){ + super.optimizeForContinuousPageFetch(namespace, ttlSeconds); + return this; + } + + public OrderStatusRequest optimizePaginationWithIdSet(){ + super.optimizePaginationWithIdSet(); + return this; + } + + public OrderStatusRequest optimizePaginationWithIdSet( + String namespace, int ttlSeconds, int maxIds){ + super.optimizePaginationWithIdSet(namespace, ttlSeconds, maxIds); + return this; + } + + public OrderStatusRequest topNProbeParentThreshold(int threshold){ + super.topNProbeParentThreshold(threshold); + return this; + } + + public OrderStatusRequest appendSearchCriteria(SearchCriteria searchCriteria){ + return (OrderStatusRequest)super.appendSearchCriteria(searchCriteria); + } + + public OrderStatusRequest filter(String property1, Operator operator, String property2){ + return appendSearchCriteria(new TwoOperatorCriteria(operator, new PropertyReference(property1), new PropertyReference(property2))); + } + + + public OrderStatusRequest matchingAnyOf(OrderStatusRequest orderStatus){ + super.internalMatchAny(orderStatus); + return this; + } + + public OrderStatusRequest enhanceChildrenIfNeeded(){ + return this; + } + + public OrderStatusRequest withDeletedRows(){ + super.withDeletedRows(); + return this; + } + + public OrderStatusRequest deletedRowsOnly(){ + super.deletedRowsOnly(); + return this; + } + + public OrderStatusRequest selectSelf(){ + super.selectSelf(); + return selectPlatformIdOnly().selectId().selectName().selectCode().selectColor().selectDisplayOrder().selectVersion(); + } + + public OrderStatusRequest selectSelfFields(){ + return selectSelf(); + } + + public OrderStatusRequest selectSelfWithoutParent(){ + selectId(); + selectVersion(); + selectProperty(OrderStatus.PLATFORM_PROPERTY); + selectProperty(OrderStatus.ID_PROPERTY); + selectProperty(OrderStatus.NAME_PROPERTY); + selectProperty(OrderStatus.CODE_PROPERTY); + selectProperty(OrderStatus.COLOR_PROPERTY); + selectProperty(OrderStatus.DISPLAY_ORDER_PROPERTY); + selectProperty(OrderStatus.VERSION_PROPERTY); + return this; + } + + public OrderStatusRequest selectAll(){ + super.selectAll(); + return selectPlatform().selectId().selectName().selectCode().selectColor().selectDisplayOrder().selectVersion(); + } + + public OrderStatusRequest selectChildren(){ + super.selectAny(); + selectCustomerOrderList(); + return selectPlatform().selectId().selectName().selectCode().selectColor().selectDisplayOrder().selectVersion(); + } + + + public OrderStatusRequest selectPlatformIdOnly(){ + selectProperty(OrderStatus.PLATFORM_PROPERTY); + return this; + } + + public OrderStatusRequest selectPlatform(){ + return selectPlatformWith(Q.platforms().unlimited().selectSelf()); + } + + public OrderStatusRequest selectPlatformWith(PlatformRequest platform){ + selectProperty(OrderStatus.PLATFORM_PROPERTY); + enhanceRelation(OrderStatus.PLATFORM_PROPERTY, platform, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, "selectPlatformWith", null, OrderStatus.PLATFORM_PROPERTY)); + return this; + } + + public OrderStatusRequest unselectPlatform(){ + unselectProperty(OrderStatus.PLATFORM_PROPERTY); + return this; + } + public OrderStatusRequest selectId(){ + selectProperty(OrderStatus.ID_PROPERTY); + return this; + } + + /** + * fill the id with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS id) to fetch id property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public OrderStatusRequest unselectId(){ + unselectProperty(OrderStatus.ID_PROPERTY); + return this; + } + public OrderStatusRequest selectName(){ + selectProperty(OrderStatus.NAME_PROPERTY); + return this; + } + + /** + * fill the name with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS name) to fetch name property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public OrderStatusRequest unselectName(){ + unselectProperty(OrderStatus.NAME_PROPERTY); + return this; + } + public OrderStatusRequest selectCode(){ + selectProperty(OrderStatus.CODE_PROPERTY); + return this; + } + + /** + * fill the code with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS code) to fetch code property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public OrderStatusRequest unselectCode(){ + unselectProperty(OrderStatus.CODE_PROPERTY); + return this; + } + public OrderStatusRequest selectColor(){ + selectProperty(OrderStatus.COLOR_PROPERTY); + return this; + } + + /** + * fill the color with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS color) to fetch color property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public OrderStatusRequest unselectColor(){ + unselectProperty(OrderStatus.COLOR_PROPERTY); + return this; + } + public OrderStatusRequest selectDisplayOrder(){ + selectProperty(OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + + /** + * fill the displayOrder with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS displayOrder) to fetch displayOrder property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + /** + * fill the displayOrder with customized aggrFunction, TEAQL uses ({aggrFunction}(displayOrder) AS displayOrder to fetch displayOrder property. + * @param aggrFunction aggrFunction + */ + public OrderStatusRequest selectDisplayOrder(AggrFunction aggrFunction){ + selectProperty(OrderStatus.DISPLAY_ORDER_PROPERTY, aggrFunction); + return this; + } + + + public OrderStatusRequest unselectDisplayOrder(){ + unselectProperty(OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + public OrderStatusRequest selectVersion(){ + selectProperty(OrderStatus.VERSION_PROPERTY); + return this; + } + + /** + * fill the version with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS version) to fetch version property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public OrderStatusRequest unselectVersion(){ + unselectProperty(OrderStatus.VERSION_PROPERTY); + return this; + } + public OrderStatusRequest selectCustomerOrderList(){ + return selectCustomerOrderListWith(Q.customerOrders().selectSelf()); + } + + public OrderStatusRequest selectCustomerOrderListWith(CustomerOrderRequest customerOrderList){ + enhanceRelation(OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY, customerOrderList, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, "selectCustomerOrderListWith", null, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest filterByPlatform(Platform... platform){ + if (platform == null || platform.length == 0) { + throw new IllegalArgumentException("filterByPlatform parameter platform cannot be empty"); + } + return appendSearchCriteria(createPlatformCriteria(Operator.EQUAL, (Object[])platform)); + } + + public OrderStatusRequest withPlatform(Operator operator, Object... values){ + return appendSearchCriteria(createPlatformCriteria(operator, values)); + } + + public OrderStatusRequest withPlatformIsUnknown(){ + return withPlatform(Operator.IS_NULL); + } + + public OrderStatusRequest withPlatformIsKnown(){ + return withPlatform(Operator.IS_NOT_NULL); + } + + public SearchCriteria createPlatformCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderStatus.PLATFORM_PROPERTY, operator, values); + } + + public OrderStatusRequest filterByPlatform(Long platform){ + if(platform == null){ + return this; + } + return withPlatform(Operator.EQUAL, platform); + } + public OrderStatusRequest withPlatformMatching(PlatformRequest platform){ + return appendSearchCriteria(new SubQuerySearchCriteria(OrderStatus.PLATFORM_PROPERTY, platform, Platform.ID_PROPERTY)); + } + + public OrderStatusRequest withoutPlatformMatching(PlatformRequest platform){ + return appendSearchCriteria(SearchCriteria.not( + new SubQuerySearchCriteria(OrderStatus.PLATFORM_PROPERTY, platform, Platform.ID_PROPERTY))); + } + + public OrderStatusRequest withId(Operator operator, Object... values){ + return appendSearchCriteria(createIdCriteria(operator, values)); + } + + public SearchCriteria createIdCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderStatus.ID_PROPERTY, operator, values); + } + + public OrderStatusRequest withIdIsNot(Long id){ + return withId(Operator.NOT_EQUAL, id); + } + + public OrderStatusRequest withIdIn(Long... id){ + return withId(Operator.IN, (Object[])id); + } + + public OrderStatusRequest withIdNotIn(Long... id){ + return withId(Operator.NOT_IN, (Object[])id); + } + public OrderStatusRequest withIdIs(Long id){ + return withId(Operator.EQUAL, id); + } + + + + public OrderStatusRequest filterByName(String... name){ + if (name == null || name.length == 0) { + throw new IllegalArgumentException("filterByName parameter name cannot be empty"); + } + return appendSearchCriteria(createNameCriteria(Operator.EQUAL, (Object[])name)); + } + + public OrderStatusRequest withName(Operator operator, Object... values){ + return appendSearchCriteria(createNameCriteria(operator, values)); + } + + public OrderStatusRequest withNameIsUnknown(){ + return withName(Operator.IS_NULL); + } + + public OrderStatusRequest withNameIsKnown(){ + return withName(Operator.IS_NOT_NULL); + } + + public SearchCriteria createNameCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderStatus.NAME_PROPERTY, operator, values); + } + + public OrderStatusRequest withNameIsNot(String name){ + return withName(Operator.NOT_EQUAL, name); + } + + public OrderStatusRequest withNameIn(String... name){ + return withName(Operator.IN, (Object[])name); + } + + public OrderStatusRequest withNameNotIn(String... name){ + return withName(Operator.NOT_IN, (Object[])name); + } + public OrderStatusRequest withNameGreaterThan(String name){ + return withName(Operator.GREATER_THAN, name); + } + + public OrderStatusRequest withNameGreaterThanOrEqualTo(String name){ + return withName(Operator.GREATER_THAN_OR_EQUAL, name); + } + + public OrderStatusRequest withNameLessThan(String name){ + return withName(Operator.LESS_THAN, name); + } + + public OrderStatusRequest withNameLessThanOrEqualTo(String name){ + return withName(Operator.LESS_THAN_OR_EQUAL, name); + } + + public OrderStatusRequest withNameBetween(String startOfName, String endOfName){ + return withName(Operator.BETWEEN, startOfName, endOfName); + } + public OrderStatusRequest withNameStartingWith(String name){ + return withName(Operator.BEGIN_WITH, name); + } + public OrderStatusRequest withNameContaining(String name){ + return withName(Operator.CONTAIN, name); + } + + public OrderStatusRequest withNameNotContaining(String name){ + return withName(Operator.NOT_CONTAIN, name); + } + + public OrderStatusRequest withNameNotStartingWith(String name){ + return withName(Operator.NOT_BEGIN_WITH, name); + } + + public OrderStatusRequest withNameEndingWith(String name){ + return withName(Operator.END_WITH, name); + } + + public OrderStatusRequest withNameNotEndingWith(String name){ + return withName(Operator.NOT_END_WITH, name); + } + + public OrderStatusRequest withNameIs(String name){ + return withName(Operator.EQUAL, name); + } + + public OrderStatusRequest withNameSoundingLike(String name){ + return withName(Operator.SOUNDS_LIKE, name); + } + + + + public OrderStatusRequest filterByCode(String... code){ + if (code == null || code.length == 0) { + throw new IllegalArgumentException("filterByCode parameter code cannot be empty"); + } + return appendSearchCriteria(createCodeCriteria(Operator.EQUAL, (Object[])code)); + } + + public OrderStatusRequest withCode(Operator operator, Object... values){ + return appendSearchCriteria(createCodeCriteria(operator, values)); + } + + public OrderStatusRequest withCodeIsUnknown(){ + return withCode(Operator.IS_NULL); + } + + public OrderStatusRequest withCodeIsKnown(){ + return withCode(Operator.IS_NOT_NULL); + } + + public SearchCriteria createCodeCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderStatus.CODE_PROPERTY, operator, values); + } + + public OrderStatusRequest withCodeIsNot(String code){ + return withCode(Operator.NOT_EQUAL, code); + } + + public OrderStatusRequest withCodeIn(String... code){ + return withCode(Operator.IN, (Object[])code); + } + + public OrderStatusRequest withCodeNotIn(String... code){ + return withCode(Operator.NOT_IN, (Object[])code); + } + public OrderStatusRequest withCodeGreaterThan(String code){ + return withCode(Operator.GREATER_THAN, code); + } + + public OrderStatusRequest withCodeGreaterThanOrEqualTo(String code){ + return withCode(Operator.GREATER_THAN_OR_EQUAL, code); + } + + public OrderStatusRequest withCodeLessThan(String code){ + return withCode(Operator.LESS_THAN, code); + } + + public OrderStatusRequest withCodeLessThanOrEqualTo(String code){ + return withCode(Operator.LESS_THAN_OR_EQUAL, code); + } + + public OrderStatusRequest withCodeBetween(String startOfCode, String endOfCode){ + return withCode(Operator.BETWEEN, startOfCode, endOfCode); + } + public OrderStatusRequest withCodeStartingWith(String code){ + return withCode(Operator.BEGIN_WITH, code); + } + public OrderStatusRequest withCodeContaining(String code){ + return withCode(Operator.CONTAIN, code); + } + + public OrderStatusRequest withCodeNotContaining(String code){ + return withCode(Operator.NOT_CONTAIN, code); + } + + public OrderStatusRequest withCodeNotStartingWith(String code){ + return withCode(Operator.NOT_BEGIN_WITH, code); + } + + public OrderStatusRequest withCodeEndingWith(String code){ + return withCode(Operator.END_WITH, code); + } + + public OrderStatusRequest withCodeNotEndingWith(String code){ + return withCode(Operator.NOT_END_WITH, code); + } + + public OrderStatusRequest withCodeIs(String code){ + return withCode(Operator.EQUAL, code); + } + + public OrderStatusRequest withCodeSoundingLike(String code){ + return withCode(Operator.SOUNDS_LIKE, code); + } + + + + public OrderStatusRequest filterByColor(String... color){ + if (color == null || color.length == 0) { + throw new IllegalArgumentException("filterByColor parameter color cannot be empty"); + } + return appendSearchCriteria(createColorCriteria(Operator.EQUAL, (Object[])color)); + } + + public OrderStatusRequest withColor(Operator operator, Object... values){ + return appendSearchCriteria(createColorCriteria(operator, values)); + } + + public OrderStatusRequest withColorIsUnknown(){ + return withColor(Operator.IS_NULL); + } + + public OrderStatusRequest withColorIsKnown(){ + return withColor(Operator.IS_NOT_NULL); + } + + public SearchCriteria createColorCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderStatus.COLOR_PROPERTY, operator, values); + } + + public OrderStatusRequest withColorIsNot(String color){ + return withColor(Operator.NOT_EQUAL, color); + } + + public OrderStatusRequest withColorIn(String... color){ + return withColor(Operator.IN, (Object[])color); + } + + public OrderStatusRequest withColorNotIn(String... color){ + return withColor(Operator.NOT_IN, (Object[])color); + } + public OrderStatusRequest withColorGreaterThan(String color){ + return withColor(Operator.GREATER_THAN, color); + } + + public OrderStatusRequest withColorGreaterThanOrEqualTo(String color){ + return withColor(Operator.GREATER_THAN_OR_EQUAL, color); + } + + public OrderStatusRequest withColorLessThan(String color){ + return withColor(Operator.LESS_THAN, color); + } + + public OrderStatusRequest withColorLessThanOrEqualTo(String color){ + return withColor(Operator.LESS_THAN_OR_EQUAL, color); + } + + public OrderStatusRequest withColorBetween(String startOfColor, String endOfColor){ + return withColor(Operator.BETWEEN, startOfColor, endOfColor); + } + public OrderStatusRequest withColorStartingWith(String color){ + return withColor(Operator.BEGIN_WITH, color); + } + public OrderStatusRequest withColorContaining(String color){ + return withColor(Operator.CONTAIN, color); + } + + public OrderStatusRequest withColorNotContaining(String color){ + return withColor(Operator.NOT_CONTAIN, color); + } + + public OrderStatusRequest withColorNotStartingWith(String color){ + return withColor(Operator.NOT_BEGIN_WITH, color); + } + + public OrderStatusRequest withColorEndingWith(String color){ + return withColor(Operator.END_WITH, color); + } + + public OrderStatusRequest withColorNotEndingWith(String color){ + return withColor(Operator.NOT_END_WITH, color); + } + + public OrderStatusRequest withColorIs(String color){ + return withColor(Operator.EQUAL, color); + } + + public OrderStatusRequest withColorSoundingLike(String color){ + return withColor(Operator.SOUNDS_LIKE, color); + } + + + + public OrderStatusRequest filterByDisplayOrder(BigDecimal... displayOrder){ + if (displayOrder == null || displayOrder.length == 0) { + throw new IllegalArgumentException("filterByDisplayOrder parameter displayOrder cannot be empty"); + } + return appendSearchCriteria(createDisplayOrderCriteria(Operator.EQUAL, (Object[])displayOrder)); + } + + public OrderStatusRequest withDisplayOrder(Operator operator, Object... values){ + return appendSearchCriteria(createDisplayOrderCriteria(operator, values)); + } + + public OrderStatusRequest withDisplayOrderIsUnknown(){ + return withDisplayOrder(Operator.IS_NULL); + } + + public OrderStatusRequest withDisplayOrderIsKnown(){ + return withDisplayOrder(Operator.IS_NOT_NULL); + } + + public SearchCriteria createDisplayOrderCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderStatus.DISPLAY_ORDER_PROPERTY, operator, values); + } + + public OrderStatusRequest withDisplayOrderIs(BigDecimal displayOrder){ + return withDisplayOrder(Operator.EQUAL, displayOrder); + } + + public OrderStatusRequest withDisplayOrderIsNot(BigDecimal displayOrder){ + return withDisplayOrder(Operator.NOT_EQUAL, displayOrder); + } + + public OrderStatusRequest withDisplayOrderIn(BigDecimal... displayOrder){ + return withDisplayOrder(Operator.IN, (Object[])displayOrder); + } + + public OrderStatusRequest withDisplayOrderNotIn(BigDecimal... displayOrder){ + return withDisplayOrder(Operator.NOT_IN, (Object[])displayOrder); + } + public OrderStatusRequest withDisplayOrderGreaterThan(BigDecimal displayOrder){ + return withDisplayOrder(Operator.GREATER_THAN, displayOrder); + } + + public OrderStatusRequest withDisplayOrderGreaterThanOrEqualTo(BigDecimal displayOrder){ + return withDisplayOrder(Operator.GREATER_THAN_OR_EQUAL, displayOrder); + } + + public OrderStatusRequest withDisplayOrderLessThan(BigDecimal displayOrder){ + return withDisplayOrder(Operator.LESS_THAN, displayOrder); + } + + public OrderStatusRequest withDisplayOrderLessThanOrEqualTo(BigDecimal displayOrder){ + return withDisplayOrder(Operator.LESS_THAN_OR_EQUAL, displayOrder); + } + + public OrderStatusRequest withDisplayOrderBetween(BigDecimal startOfDisplayOrder, BigDecimal endOfDisplayOrder){ + return withDisplayOrder(Operator.BETWEEN, startOfDisplayOrder, endOfDisplayOrder); + } + + + + public OrderStatusRequest filterByVersion(Long... version){ + if (version == null || version.length == 0) { + throw new IllegalArgumentException("filterByVersion parameter version cannot be empty"); + } + return appendSearchCriteria(createVersionCriteria(Operator.EQUAL, (Object[])version)); + } + + public OrderStatusRequest withVersion(Operator operator, Object... values){ + return appendSearchCriteria(createVersionCriteria(operator, values)); + } + + public OrderStatusRequest withVersionIsUnknown(){ + return withVersion(Operator.IS_NULL); + } + + public OrderStatusRequest withVersionIsKnown(){ + return withVersion(Operator.IS_NOT_NULL); + } + + public SearchCriteria createVersionCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(OrderStatus.VERSION_PROPERTY, operator, values); + } + + public OrderStatusRequest withVersionIs(Long version){ + return withVersion(Operator.EQUAL, version); + } + + public OrderStatusRequest withVersionIsNot(Long version){ + return withVersion(Operator.NOT_EQUAL, version); + } + + public OrderStatusRequest withVersionIn(Long... version){ + return withVersion(Operator.IN, (Object[])version); + } + + public OrderStatusRequest withVersionNotIn(Long... version){ + return withVersion(Operator.NOT_IN, (Object[])version); + } + public OrderStatusRequest withVersionGreaterThan(Long version){ + return withVersion(Operator.GREATER_THAN, version); + } + + public OrderStatusRequest withVersionGreaterThanOrEqualTo(Long version){ + return withVersion(Operator.GREATER_THAN_OR_EQUAL, version); + } + + public OrderStatusRequest withVersionLessThan(Long version){ + return withVersion(Operator.LESS_THAN, version); + } + + public OrderStatusRequest withVersionLessThanOrEqualTo(Long version){ + return withVersion(Operator.LESS_THAN_OR_EQUAL, version); + } + + public OrderStatusRequest withVersionBetween(Long startOfVersion, Long endOfVersion){ + return withVersion(Operator.BETWEEN, startOfVersion, endOfVersion); + } + + + public OrderStatusRequest withCustomerOrderListMatching(CustomerOrderRequest customerOrderRequest){ + return appendSearchCriteria(new SubQuerySearchCriteria(OrderStatus.ID_PROPERTY, customerOrderRequest, CustomerOrder.STATUS_PROPERTY)); + } + + public OrderStatusRequest withoutCustomerOrderListMatching(CustomerOrderRequest customerOrderRequest){ + return appendSearchCriteria(SearchCriteria.not(new SubQuerySearchCriteria(OrderStatus.ID_PROPERTY, customerOrderRequest, CustomerOrder.STATUS_PROPERTY))); + } + + public OrderStatusRequest haveCustomerOrders(){ + return withCustomerOrderListMatching(Q.customerOrders().unlimited()); + } + + public OrderStatusRequest haveNoCustomerOrders(){ + return withoutCustomerOrderListMatching(Q.customerOrders().unlimited()); + } + + public OrderStatusRequest count(){ + super.count(); + setPageCountOperation(io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.NAMED_COUNT, "count", null, null)); + return this; + } + public OrderStatusRequest countAs(String retName){ + super.count(retName); + setPageCountOperation(io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.NAMED_COUNT, "countAs", retName, null)); + return this; + } + public OrderStatusRequest minDisplayOrder(){ + return minDisplayOrderAs(prefix("minOf",OrderStatus.DISPLAY_ORDER_PROPERTY)); + } + + public OrderStatusRequest minDisplayOrderAs(String retName){ + super.min(retName, OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + public OrderStatusRequest maxDisplayOrder(){ + return maxDisplayOrderAs(prefix("maxOf",OrderStatus.DISPLAY_ORDER_PROPERTY)); + } + + public OrderStatusRequest maxDisplayOrderAs(String retName){ + super.max(retName, OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + public OrderStatusRequest sumDisplayOrder(){ + return sumDisplayOrderAs(prefix("sumOf",OrderStatus.DISPLAY_ORDER_PROPERTY)); + } + + public OrderStatusRequest sumDisplayOrderAs(String retName){ + super.sum(retName, OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + public OrderStatusRequest avgDisplayOrder(){ + return avgDisplayOrderAs(prefix("avgOf",OrderStatus.DISPLAY_ORDER_PROPERTY)); + } + + public OrderStatusRequest avgDisplayOrderAs(String retName){ + super.avg(retName, OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + public OrderStatusRequest standardDeviationDisplayOrder(){ + return standardDeviationDisplayOrderAs(prefix("standardDeviationOf",OrderStatus.DISPLAY_ORDER_PROPERTY)); + } + + public OrderStatusRequest standardDeviationDisplayOrderAs(String retName){ + super.standardDeviation(retName, OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + public OrderStatusRequest squareRootOfPopulationStandardDeviationDisplayOrder(){ + return squareRootOfPopulationStandardDeviationDisplayOrderAs(prefix("squareRootOfPopulationStandardDeviationOf",OrderStatus.DISPLAY_ORDER_PROPERTY)); + } + + public OrderStatusRequest squareRootOfPopulationStandardDeviationDisplayOrderAs(String retName){ + super.squareRootOfPopulationStandardDeviation(retName, OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + public OrderStatusRequest sampleVarianceDisplayOrder(){ + return sampleVarianceDisplayOrderAs(prefix("sampleVarianceOf",OrderStatus.DISPLAY_ORDER_PROPERTY)); + } + + public OrderStatusRequest sampleVarianceDisplayOrderAs(String retName){ + super.sampleVariance(retName, OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + public OrderStatusRequest samplePopulationVarianceDisplayOrder(){ + return samplePopulationVarianceDisplayOrderAs(prefix("samplePopulationVarianceOf",OrderStatus.DISPLAY_ORDER_PROPERTY)); + } + + public OrderStatusRequest samplePopulationVarianceDisplayOrderAs(String retName){ + super.samplePopulationVariance(retName, OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + public OrderStatusRequest groupByPlatformWithDetails(){ + return groupByPlatformWithDetails(Q.platforms().unlimited()); + } + + public OrderStatusRequest groupByPlatformWithDetails(PlatformRequest subRequest){ + aggregate(OrderStatus.PLATFORM_PROPERTY, subRequest); + return this; + } + + + + + + + + public OrderStatusRequest groupByCustomerOrdersWithDetails(CustomerOrderRequest subRequest){ + aggregate(OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY, subRequest); + return this; + } + + public OrderStatusRequest groupByPlatformWith(PlatformRequest subRequest){ + groupBy(OrderStatus.PLATFORM_PROPERTY, subRequest); + return this; + } + public OrderStatusRequest groupByPlatform(){ + groupBy(OrderStatus.PLATFORM_PROPERTY); + return this; + } + + public OrderStatusRequest groupByPlatformAs(String retName){ + groupBy(retName, OrderStatus.PLATFORM_PROPERTY); + return this; + } + + public OrderStatusRequest groupByPlatformWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderStatus.PLATFORM_PROPERTY, function); + return this; + } + + public OrderStatusRequest groupById(){ + groupBy(OrderStatus.ID_PROPERTY); + return this; + } + + public OrderStatusRequest groupByIdAs(String retName){ + groupBy(retName, OrderStatus.ID_PROPERTY); + return this; + } + + public OrderStatusRequest groupByIdWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderStatus.ID_PROPERTY, function); + return this; + } + + public OrderStatusRequest groupByName(){ + groupBy(OrderStatus.NAME_PROPERTY); + return this; + } + + public OrderStatusRequest groupByNameAs(String retName){ + groupBy(retName, OrderStatus.NAME_PROPERTY); + return this; + } + + public OrderStatusRequest groupByNameWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderStatus.NAME_PROPERTY, function); + return this; + } + + public OrderStatusRequest groupByCode(){ + groupBy(OrderStatus.CODE_PROPERTY); + return this; + } + + public OrderStatusRequest groupByCodeAs(String retName){ + groupBy(retName, OrderStatus.CODE_PROPERTY); + return this; + } + + public OrderStatusRequest groupByCodeWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderStatus.CODE_PROPERTY, function); + return this; + } + + public OrderStatusRequest groupByColor(){ + groupBy(OrderStatus.COLOR_PROPERTY); + return this; + } + + public OrderStatusRequest groupByColorAs(String retName){ + groupBy(retName, OrderStatus.COLOR_PROPERTY); + return this; + } + + public OrderStatusRequest groupByColorWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderStatus.COLOR_PROPERTY, function); + return this; + } + + public OrderStatusRequest groupByDisplayOrder(){ + groupBy(OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + + public OrderStatusRequest groupByDisplayOrderAs(String retName){ + groupBy(retName, OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + + public OrderStatusRequest groupByDisplayOrderWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderStatus.DISPLAY_ORDER_PROPERTY, function); + return this; + } + + public OrderStatusRequest groupByVersion(){ + groupBy(OrderStatus.VERSION_PROPERTY); + return this; + } + + public OrderStatusRequest groupByVersionAs(String retName){ + groupBy(retName, OrderStatus.VERSION_PROPERTY); + return this; + } + + public OrderStatusRequest groupByVersionWithFunction(String retName, AggrFunction function){ + groupBy(retName, OrderStatus.VERSION_PROPERTY, function); + return this; + } + + + + public OrderStatusRequest orderByPlatformAscending(){ + addOrderByAscending(OrderStatus.PLATFORM_PROPERTY); + return this; + } + + public OrderStatusRequest orderByPlatformDescending(){ + addOrderByDescending(OrderStatus.PLATFORM_PROPERTY); + return this; + } + + public OrderStatusRequest orderByIdAscending(){ + addOrderByAscending(OrderStatus.ID_PROPERTY); + return this; + } + + public OrderStatusRequest orderByIdDescending(){ + addOrderByDescending(OrderStatus.ID_PROPERTY); + return this; + } + + public OrderStatusRequest orderByNameAscending(){ + addOrderByAscending(OrderStatus.NAME_PROPERTY); + return this; + } + + public OrderStatusRequest orderByNameDescending(){ + addOrderByDescending(OrderStatus.NAME_PROPERTY); + return this; + } + public OrderStatusRequest orderByNameAscendingUsingGBK(){ + addOrderByAscendingUsingGBK(OrderStatus.NAME_PROPERTY); + return this; + } + + public OrderStatusRequest orderByNameDescendingUsingGBK(){ + addOrderByDescendingUsingGBK(OrderStatus.NAME_PROPERTY); + return this; + } + public OrderStatusRequest orderByCodeAscending(){ + addOrderByAscending(OrderStatus.CODE_PROPERTY); + return this; + } + + public OrderStatusRequest orderByCodeDescending(){ + addOrderByDescending(OrderStatus.CODE_PROPERTY); + return this; + } + public OrderStatusRequest orderByCodeAscendingUsingGBK(){ + addOrderByAscendingUsingGBK(OrderStatus.CODE_PROPERTY); + return this; + } + + public OrderStatusRequest orderByCodeDescendingUsingGBK(){ + addOrderByDescendingUsingGBK(OrderStatus.CODE_PROPERTY); + return this; + } + public OrderStatusRequest orderByColorAscending(){ + addOrderByAscending(OrderStatus.COLOR_PROPERTY); + return this; + } + + public OrderStatusRequest orderByColorDescending(){ + addOrderByDescending(OrderStatus.COLOR_PROPERTY); + return this; + } + public OrderStatusRequest orderByColorAscendingUsingGBK(){ + addOrderByAscendingUsingGBK(OrderStatus.COLOR_PROPERTY); + return this; + } + + public OrderStatusRequest orderByColorDescendingUsingGBK(){ + addOrderByDescendingUsingGBK(OrderStatus.COLOR_PROPERTY); + return this; + } + public OrderStatusRequest orderByDisplayOrderAscending(){ + addOrderByAscending(OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + + public OrderStatusRequest orderByDisplayOrderDescending(){ + addOrderByDescending(OrderStatus.DISPLAY_ORDER_PROPERTY); + return this; + } + + public OrderStatusRequest orderByVersionAscending(){ + addOrderByAscending(OrderStatus.VERSION_PROPERTY); + return this; + } + + public OrderStatusRequest orderByVersionDescending(){ + addOrderByDescending(OrderStatus.VERSION_PROPERTY); + return this; + } + + + public OrderStatusRequest statsFromCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + return statsFromCustomerOrdersAs(name, subRequest, false); + } + + public OrderStatusRequest statsFromCustomerOrdersAs(String name, CustomerOrderRequest subRequest, boolean singleResult){ + addRelationAggregate(name, subRequest, CustomerOrder.STATUS_PROPERTY, singleResult, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "statsFromCustomerOrdersAs", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest statsFromCustomerOrders(CustomerOrderRequest subRequest){ + return statsFromCustomerOrdersAs(REFINEMENTS, subRequest); + } + public PlatformRequest rollUpToPlatform(){ + PlatformRequest platform = Q.platforms().unlimited(); + this.withPlatformMatching(platform) + .groupByPlatformWith(platform); + return platform; + } + + + + + + + + public OrderStatusRequest countCustomerOrders(){ + addRelationCount("Count", Q.customerOrders().unlimited(), CustomerOrder.STATUS_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countCustomerOrders", null, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest countCustomerOrdersAs(String name){ + addRelationCount(name, Q.customerOrders().unlimited(), CustomerOrder.STATUS_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countCustomerOrdersAs", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest countCustomerOrdersWith(String name, CustomerOrderRequest subRequest){ + addRelationCount(name, subRequest, CustomerOrder.STATUS_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countCustomerOrdersWith", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public OrderStatusRequest minTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("minTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.STATUS_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("min".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "minTotalAmountOfCustomerOrders", null, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest minTotalAmountOfCustomerOrdersAs(String name){ + return minTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public OrderStatusRequest minTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.STATUS_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("min".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "minTotalAmountOfCustomerOrdersAs", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public OrderStatusRequest maxTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("maxTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.STATUS_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("max".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "maxTotalAmountOfCustomerOrders", null, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest maxTotalAmountOfCustomerOrdersAs(String name){ + return maxTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public OrderStatusRequest maxTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.STATUS_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("max".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "maxTotalAmountOfCustomerOrdersAs", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public OrderStatusRequest sumTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("sumTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.STATUS_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("sum".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sumTotalAmountOfCustomerOrders", null, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest sumTotalAmountOfCustomerOrdersAs(String name){ + return sumTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public OrderStatusRequest sumTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.STATUS_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("sum".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sumTotalAmountOfCustomerOrdersAs", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public OrderStatusRequest avgTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("avgTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.STATUS_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("avg".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "avgTotalAmountOfCustomerOrders", null, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest avgTotalAmountOfCustomerOrdersAs(String name){ + return avgTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public OrderStatusRequest avgTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.STATUS_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("avg".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "avgTotalAmountOfCustomerOrdersAs", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public OrderStatusRequest standardDeviationTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("stdDevTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.STATUS_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "standardDeviationTotalAmountOfCustomerOrders", null, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest standardDeviationTotalAmountOfCustomerOrdersAs(String name){ + return standardDeviationTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public OrderStatusRequest standardDeviationTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.STATUS_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "standardDeviationTotalAmountOfCustomerOrdersAs", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public OrderStatusRequest squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("stdDevPopTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.STATUS_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrders", null, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrdersAs(String name){ + return squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public OrderStatusRequest squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.STATUS_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "squareRootOfPopulationStandardDeviationTotalAmountOfCustomerOrdersAs", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public OrderStatusRequest sampleVarianceTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("varSampTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.STATUS_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_samp".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sampleVarianceTotalAmountOfCustomerOrders", null, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest sampleVarianceTotalAmountOfCustomerOrdersAs(String name){ + return sampleVarianceTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public OrderStatusRequest sampleVarianceTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.STATUS_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_samp".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sampleVarianceTotalAmountOfCustomerOrdersAs", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + public OrderStatusRequest samplePopulationVarianceTotalAmountOfCustomerOrders(){ + addRelationScalarAggregate("varPopTotalAmountOfCustomerOrders", Q.customerOrders().unlimited(), + CustomerOrder.STATUS_PROPERTY, CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "samplePopulationVarianceTotalAmountOfCustomerOrders", null, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest samplePopulationVarianceTotalAmountOfCustomerOrdersAs(String name){ + return samplePopulationVarianceTotalAmountOfCustomerOrdersAs(name, Q.customerOrders().unlimited()); + } + + public OrderStatusRequest samplePopulationVarianceTotalAmountOfCustomerOrdersAs(String name, CustomerOrderRequest subRequest){ + addRelationScalarAggregate(name, subRequest, CustomerOrder.STATUS_PROPERTY, + CustomerOrder.TOTAL_AMOUNT_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "samplePopulationVarianceTotalAmountOfCustomerOrdersAs", name, OrderStatus.CUSTOMER_ORDER_LIST_PROPERTY)); + return this; + } + + public OrderStatusRequest facetByPlatformAs(String facetName, PlatformRequest platform){ + return facetByPlatformAs(facetName, platform, true); + } + + public OrderStatusRequest facetByPlatformAs(String facetName, PlatformRequest platform, boolean includeAllFacets){ + addFacet(facetName, OrderStatus.PLATFORM_PROPERTY, platform, includeAllFacets, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.FACET, "facetByPlatformAs", facetName, OrderStatus.PLATFORM_PROPERTY)); + return this; + } + + + /** + * get topN records + * @param topN records number + */ + public OrderStatusRequest top(int topN) { + super.top(topN); + return this; + } + + /** Cross-runtime bounded-query alias. */ + public OrderStatusRequest limit(int limit) { + return top(limit); + } + + /** + * get records from offset(inclusive) to offset+size(exclusive) + * @param offset record offset + * @param size records number + */ + public OrderStatusRequest offset(int offset, int size) { + super.offset(offset, size); + return this; + } + + /** + * retrieve all records + */ + public OrderStatusRequest unlimited() { + super.unlimited(); + return this; + } + + /** + * get records of one page + * @param pageNumber page number(1-based) + * @param pageSize page size + */ + public OrderStatusRequest page(int pageNumber, int pageSize) { + int offset = (pageNumber - 1) * pageSize; + return offset(offset, pageSize); + } + + /** + * get records of one page, default page size is 10 + * @param pageNumber page number(1-based) + */ + public OrderStatusRequest page(int pageNumber) { + return page(pageNumber, 10); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/Platform.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/Platform.java new file mode 100644 index 00000000..5352b41f --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/Platform.java @@ -0,0 +1,142 @@ + +package com.example.requesttreeorderservice.platform; + +import com.example.requesttreeorderservice.customer.Customer; +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import io.teaql.core.Audited; +import io.teaql.core.BaseEntity; +import io.teaql.core.EntityStatus; +import io.teaql.core.FieldLayout; +import io.teaql.core.FrameworkInternal; +import io.teaql.core.RemoteInput; +import io.teaql.core.SmartList; +import java.util.Objects; + +/** + * [TEAQL AI WARNING] + * TeaQL was explicitly designed to PREVENT AI hallucinations and random guessing. + * DO NOT GUESS METHOD NAMES! + * The methods listed below are the ONLY valid ways to interact with this entity. + * If you encounter compilation errors (e.g., method not found), DO NOT guess another method name. + * Read the method signatures in this file before proceeding. + */ +public class Platform extends BaseEntity implements RemoteInput { + public static String INTERNAL_TYPE = "Platform"; + + // Framework metadata: complete fixed layout, independent of SQL/projection order. + public static final String __TEAQL_FIELD_LAYOUT_REVISION = "5fc644cb1f2eea3001a51825735535d27d0cefa47fe9e256c92d24309fc93c0f"; + public static final java.util.Map __TEAQL_FIXED_FIELD_INDEXES = + java.util.Map.ofEntries(java.util.Map.entry("id", 0), java.util.Map.entry("name", 1), java.util.Map.entry("version", 2)); + public static final java.util.Map> __TEAQL_FIXED_FIELD_MAPPINGS = + java.util.Map.ofEntries(java.util.Map.entry("id", java.util.List.of("id", "id")), java.util.Map.entry("name", java.util.List.of("name", "name")), java.util.Map.entry("version", java.util.List.of("version", "version"))); + public static final java.util.Set __TEAQL_FIXED_RELATION_NAMES = java.util.Set.of( + "orderStatusList", "customerList"); + public static final FieldLayout __TEAQL_FIELD_LAYOUT = + FieldLayout.installGenerated(FieldLayout.generated( + Platform.class, __TEAQL_FIELD_LAYOUT_REVISION, + __TEAQL_FIXED_FIELD_INDEXES, __TEAQL_FIXED_FIELD_MAPPINGS, __TEAQL_FIXED_RELATION_NAMES)); + + + public static final String NAME_PROPERTY = "name"; + public static final String ORDER_STATUS_LIST_PROPERTY = "orderStatusList"; + public static final String CUSTOMER_LIST_PROPERTY = "customerList"; + private String name; + + private SmartList orderStatusList; + private SmartList customerList; + + public String getName(){ + return this.name; + } + + public SmartList getOrderStatusList(){ + return this.orderStatusList; + } + public SmartList getCustomerList(){ + return this.customerList; + } + public Platform updateName(String name){ + name = (name == null ? null : name.trim()); + if(Objects.equals(this.name, name)){ + return this; + } + handleUpdate(NAME_PROPERTY, getName(), name); + this.name = name; + return this; + } + + public Platform addOrderStatus(OrderStatus orderStatus){ + if (orderStatus == null){ + return this; + } + + if(null == this.orderStatusList){ + this.orderStatusList = new SmartList<>(); + } + + this.orderStatusList.add(orderStatus); + orderStatus.updatePlatform(this); + return this; + } + public Platform addCustomer(Customer customer){ + if (customer == null){ + return this; + } + + if(null == this.customerList){ + this.customerList = new SmartList<>(); + } + + this.customerList.add(customer); + customer.updatePlatform(this); + return this; + } + + public static Platform refer(Long id){ + Platform refer = new Platform(); + refer.__internalSet("id", id); + refer.set$status(EntityStatus.REFER); + return refer; + } + @Override + public String typeName(){ + return INTERNAL_TYPE; + } + + public Platform comment(String comment){ + this.setComment(comment); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public Audited auditAs(String action) { + return new Audited<>(this, action); + } + + // ===== Framework Internal: generated switch dispatch ===== + @Override + @FrameworkInternal + public void __internalSet(String property, Object value) { + markPropertyLoaded(property); + switch (property) { + case "name": this.name = (value == null ? null : ((String)value).trim()); break; + + case "orderStatusList": this.orderStatusList = (SmartList) value; break; + case "customerList": this.customerList = (SmartList) value; break; + default: super.__internalSet(property, value); + } + } + + @Override + @FrameworkInternal + public Object __internalGet(String property) { + switch (property) { + case "name": return this.name; + case "orderStatusList": return this.orderStatusList; + case "customerList": return this.customerList; + default: return super.__internalGet(property); + } + } + +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformChecker.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformChecker.java new file mode 100644 index 00000000..095fe8f8 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformChecker.java @@ -0,0 +1,55 @@ + +package com.example.requesttreeorderservice.platform; + +import com.example.requesttreeorderservice.customer.Customer; +import com.example.requesttreeorderservice.customer.CustomerChecker; +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import com.example.requesttreeorderservice.orderstatus.OrderStatusChecker; +import io.teaql.core.UserContext; +import io.teaql.core.checker.Checker; +import io.teaql.core.checker.ObjectLocation; + +public class PlatformChecker implements Checker{ + + public String type(){ + return Platform.INTERNAL_TYPE; + } + + public void checkAndFix(UserContext _context, Platform platform, ObjectLocation _parentLocation){ + if(needCheck(_context, platform)){ + markAsChecked(_context, platform); + doCheck(_context, platform, _parentLocation); + } + } + + public void doCheck(UserContext _context, Platform platform, ObjectLocation _parentLocation){ + if((platform == null)){ + return; + } + if(platform.newItem()){ + }else if(platform.updateItem()){ + if(!platform.isPropertyLoaded("name")){ + invalidTypeCheck(_context, newLocation(_parentLocation, "name"), "Mutation requires a fully loaded entity"); + } + + } + checkName(_context, platform.getProperty(Platform.NAME_PROPERTY), newLocation(_parentLocation, "name")); + for(int i = 0; platform.getOrderStatusList() != null && i < platform.getOrderStatusList().size(); i++){ + OrderStatus orderStatus = platform.getOrderStatusList().get(i); + new OrderStatusChecker().checkAndFix(_context, orderStatus, newLocation(_parentLocation, "order_status_list", i)); + } + for(int i = 0; platform.getCustomerList() != null && i < platform.getCustomerList().size(); i++){ + Customer customer = platform.getCustomerList().get(i); + new CustomerChecker().checkAndFix(_context, customer, newLocation(_parentLocation, "customer_list", i)); + } + } + + public void checkName(UserContext _context, String name, ObjectLocation _parentLocation){ + requiredCheck(_context, _parentLocation, name); + if((name == null)){ + return; + } + maxStringCheck(_context, _parentLocation, 100, name); + + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformExpression.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformExpression.java new file mode 100644 index 00000000..9db873a1 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformExpression.java @@ -0,0 +1,59 @@ + +package com.example.requesttreeorderservice.platform; + +import com.example.requesttreeorderservice.customer.Customer; +import com.example.requesttreeorderservice.customer.CustomerListExpression; +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import com.example.requesttreeorderservice.orderstatus.OrderStatusListExpression; +import io.teaql.core.UserContext; +import io.teaql.core.value.BaseEntityExpression; +import io.teaql.core.value.Expression; +import io.teaql.core.value.ExpressionAdaptor; +import java.util.function.Function; + +public class PlatformExpression extends ExpressionAdaptor implements BaseEntityExpression { + public PlatformExpression(Expression expression){ + super(expression); + } + + public PlatformExpression(Expression expression, Function function){ + super(expression, function); + } + + public PlatformExpression updateId(Long id){ + return new PlatformExpression(this, $it -> {((Platform)$it).__internalSet("id", id); return this;}); + } + + public PlatformExpression save(UserContext userContext){ + return new PlatformExpression(this, $it -> ((Platform)$it).auditAs("Saved by Expression").save(userContext)); + } + + public PlatformExpression save(String intent, UserContext userContext){ + return new PlatformExpression(this, $it -> ((Platform)$it).auditAs(intent).save(userContext)); + } + + public boolean isNull() { + return resolve() == null; + } + + + public Expression getName(){ + return loaded("name", Platform::getName); + } + public PlatformExpression updateName(String name){ + return new PlatformExpression(this, $it -> ((Platform)$it).updateName(name)); + } + + public OrderStatusListExpression getOrderStatusList(){ + return new OrderStatusListExpression(loaded("orderStatusList", Platform::getOrderStatusList)); + } + public CustomerListExpression getCustomerList(){ + return new CustomerListExpression(loaded("customerList", Platform::getCustomerList)); + } + public PlatformExpression addOrderStatus(OrderStatus orderStatus){ + return new PlatformExpression(this, $it -> ((Platform)$it).addOrderStatus(orderStatus)); + } + public PlatformExpression addCustomer(Customer customer){ + return new PlatformExpression(this, $it -> ((Platform)$it).addCustomer(customer)); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformListExpression.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformListExpression.java new file mode 100644 index 00000000..67a967af --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformListExpression.java @@ -0,0 +1,25 @@ + +package com.example.requesttreeorderservice.platform; + +import io.teaql.core.SmartList; +import io.teaql.core.value.Expression; +import io.teaql.core.value.SmartListExpression; +import java.util.function.Function; + +public class PlatformListExpression extends SmartListExpression { + public PlatformListExpression(Expression> expression){ + super(expression); + } + + public PlatformListExpression(Expression expression, Function> function){ + super(expression, function); + } + + public PlatformExpression first() { + return new PlatformExpression(super.first()); + } + + public PlatformExpression get(int index) { + return new PlatformExpression(super.get(index)); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformRequest.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformRequest.java new file mode 100644 index 00000000..18651ac4 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/platform/PlatformRequest.java @@ -0,0 +1,791 @@ + +package com.example.requesttreeorderservice.platform; + +import com.example.requesttreeorderservice.Q; +import com.example.requesttreeorderservice.customer.Customer; +import com.example.requesttreeorderservice.customer.CustomerRequest; +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import com.example.requesttreeorderservice.orderstatus.OrderStatusRequest; +import io.teaql.core.AggrFunction; +import io.teaql.core.BaseRequest; +import io.teaql.core.PropertyReference; +import io.teaql.core.SearchCriteria; +import io.teaql.core.SubQuerySearchCriteria; +import io.teaql.core.criteria.Operator; +import io.teaql.core.criteria.TwoOperatorCriteria; + +public class PlatformRequest extends BaseRequest { + + /** + * @deprecated AI agents and business code must use the generated Q facade + * instead of constructing request builders directly. + */ + @Deprecated + @SuppressWarnings("unchecked") + public PlatformRequest(Class returnType){ + this(returnType, () -> (T) new Platform()); + } + + protected PlatformRequest(Class returnType, java.util.function.Supplier factory){ + super(returnType, factory); + selectId(); + selectVersion(); + } + + public PlatformRequest comment(String comment){ + super.internalComment(comment); + return this; + } + + // purpose() 继承自 BaseRequest,返回 ExecutableRequest(终结方法) + + public PlatformRequest returnType(Class returnType){ + super.setReturnType(returnType); + return this; + } + + public PlatformRequest enableAggregationCache(long cacheExpiredMillis){ + super.enableAggregationCache(); + super.aggregateCacheTime(cacheExpiredMillis); + return this; + } + + public PlatformRequest enableAggregationCache(){ + return enableAggregationCache(0l); + } + + + public PlatformRequest propagateAggregationCache(long cacheExpiredMillis){ + super.propagateAggregationCache(cacheExpiredMillis); + return this; + } + + /** + * Accept best-effort stateful seek optimization for browsing consecutive pages. + * Do not use this for business processing that must visit every row exactly once. + */ + public PlatformRequest optimizeForContinuousPageFetch(){ + super.optimizeForContinuousPageFetch(); + return this; + } + + public PlatformRequest optimizeForContinuousPageFetch(String namespace, int ttlSeconds){ + super.optimizeForContinuousPageFetch(namespace, ttlSeconds); + return this; + } + + public PlatformRequest optimizePaginationWithIdSet(){ + super.optimizePaginationWithIdSet(); + return this; + } + + public PlatformRequest optimizePaginationWithIdSet( + String namespace, int ttlSeconds, int maxIds){ + super.optimizePaginationWithIdSet(namespace, ttlSeconds, maxIds); + return this; + } + + public PlatformRequest topNProbeParentThreshold(int threshold){ + super.topNProbeParentThreshold(threshold); + return this; + } + + public PlatformRequest appendSearchCriteria(SearchCriteria searchCriteria){ + return (PlatformRequest)super.appendSearchCriteria(searchCriteria); + } + + public PlatformRequest filter(String property1, Operator operator, String property2){ + return appendSearchCriteria(new TwoOperatorCriteria(operator, new PropertyReference(property1), new PropertyReference(property2))); + } + + + public PlatformRequest matchingAnyOf(PlatformRequest platform){ + super.internalMatchAny(platform); + return this; + } + + public PlatformRequest enhanceChildrenIfNeeded(){ + return this; + } + + public PlatformRequest withDeletedRows(){ + super.withDeletedRows(); + return this; + } + + public PlatformRequest deletedRowsOnly(){ + super.deletedRowsOnly(); + return this; + } + + public PlatformRequest selectSelf(){ + super.selectSelf(); + return selectId().selectName().selectVersion(); + } + + public PlatformRequest selectSelfFields(){ + return selectSelf(); + } + + public PlatformRequest selectSelfWithoutParent(){ + selectId(); + selectVersion(); + selectProperty(Platform.ID_PROPERTY); + selectProperty(Platform.NAME_PROPERTY); + selectProperty(Platform.VERSION_PROPERTY); + return this; + } + + public PlatformRequest selectAll(){ + super.selectAll(); + return selectId().selectName().selectVersion(); + } + + public PlatformRequest selectChildren(){ + super.selectAny(); + selectOrderStatusList().selectCustomerList(); + return selectId().selectName().selectVersion(); + } + + + public PlatformRequest selectId(){ + selectProperty(Platform.ID_PROPERTY); + return this; + } + + /** + * fill the id with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS id) to fetch id property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public PlatformRequest unselectId(){ + unselectProperty(Platform.ID_PROPERTY); + return this; + } + public PlatformRequest selectName(){ + selectProperty(Platform.NAME_PROPERTY); + return this; + } + + /** + * fill the name with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS name) to fetch name property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public PlatformRequest unselectName(){ + unselectProperty(Platform.NAME_PROPERTY); + return this; + } + public PlatformRequest selectVersion(){ + selectProperty(Platform.VERSION_PROPERTY); + return this; + } + + /** + * fill the version with customized rawSqlSegment, TEAQL uses ({rawSqlSegment} AS version) to fetch version property. + * @param rawSqlSegment customized rawSqlSegment + */ + + + + + public PlatformRequest unselectVersion(){ + unselectProperty(Platform.VERSION_PROPERTY); + return this; + } + public PlatformRequest selectOrderStatusList(){ + return selectOrderStatusListWith(Q.orderStatuses().selectSelf()); + } + + public PlatformRequest selectOrderStatusListWith(OrderStatusRequest orderStatusList){ + enhanceRelation(Platform.ORDER_STATUS_LIST_PROPERTY, orderStatusList, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, "selectOrderStatusListWith", null, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + public PlatformRequest selectCustomerList(){ + return selectCustomerListWith(Q.customers().selectSelf()); + } + + public PlatformRequest selectCustomerListWith(CustomerRequest customerList){ + enhanceRelation(Platform.CUSTOMER_LIST_PROPERTY, customerList, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, "selectCustomerListWith", null, Platform.CUSTOMER_LIST_PROPERTY)); + return this; + } + + public PlatformRequest withId(Operator operator, Object... values){ + return appendSearchCriteria(createIdCriteria(operator, values)); + } + + public SearchCriteria createIdCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(Platform.ID_PROPERTY, operator, values); + } + + public PlatformRequest withIdIsNot(Long id){ + return withId(Operator.NOT_EQUAL, id); + } + + public PlatformRequest withIdIn(Long... id){ + return withId(Operator.IN, (Object[])id); + } + + public PlatformRequest withIdNotIn(Long... id){ + return withId(Operator.NOT_IN, (Object[])id); + } + public PlatformRequest withIdIs(Long id){ + return withId(Operator.EQUAL, id); + } + + + + public PlatformRequest filterByName(String... name){ + if (name == null || name.length == 0) { + throw new IllegalArgumentException("filterByName parameter name cannot be empty"); + } + return appendSearchCriteria(createNameCriteria(Operator.EQUAL, (Object[])name)); + } + + public PlatformRequest withName(Operator operator, Object... values){ + return appendSearchCriteria(createNameCriteria(operator, values)); + } + + public PlatformRequest withNameIsUnknown(){ + return withName(Operator.IS_NULL); + } + + public PlatformRequest withNameIsKnown(){ + return withName(Operator.IS_NOT_NULL); + } + + public SearchCriteria createNameCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(Platform.NAME_PROPERTY, operator, values); + } + + public PlatformRequest withNameIsNot(String name){ + return withName(Operator.NOT_EQUAL, name); + } + + public PlatformRequest withNameIn(String... name){ + return withName(Operator.IN, (Object[])name); + } + + public PlatformRequest withNameNotIn(String... name){ + return withName(Operator.NOT_IN, (Object[])name); + } + public PlatformRequest withNameGreaterThan(String name){ + return withName(Operator.GREATER_THAN, name); + } + + public PlatformRequest withNameGreaterThanOrEqualTo(String name){ + return withName(Operator.GREATER_THAN_OR_EQUAL, name); + } + + public PlatformRequest withNameLessThan(String name){ + return withName(Operator.LESS_THAN, name); + } + + public PlatformRequest withNameLessThanOrEqualTo(String name){ + return withName(Operator.LESS_THAN_OR_EQUAL, name); + } + + public PlatformRequest withNameBetween(String startOfName, String endOfName){ + return withName(Operator.BETWEEN, startOfName, endOfName); + } + public PlatformRequest withNameStartingWith(String name){ + return withName(Operator.BEGIN_WITH, name); + } + public PlatformRequest withNameContaining(String name){ + return withName(Operator.CONTAIN, name); + } + + public PlatformRequest withNameNotContaining(String name){ + return withName(Operator.NOT_CONTAIN, name); + } + + public PlatformRequest withNameNotStartingWith(String name){ + return withName(Operator.NOT_BEGIN_WITH, name); + } + + public PlatformRequest withNameEndingWith(String name){ + return withName(Operator.END_WITH, name); + } + + public PlatformRequest withNameNotEndingWith(String name){ + return withName(Operator.NOT_END_WITH, name); + } + + public PlatformRequest withNameIs(String name){ + return withName(Operator.EQUAL, name); + } + + public PlatformRequest withNameSoundingLike(String name){ + return withName(Operator.SOUNDS_LIKE, name); + } + + + + public PlatformRequest filterByVersion(Long... version){ + if (version == null || version.length == 0) { + throw new IllegalArgumentException("filterByVersion parameter version cannot be empty"); + } + return appendSearchCriteria(createVersionCriteria(Operator.EQUAL, (Object[])version)); + } + + public PlatformRequest withVersion(Operator operator, Object... values){ + return appendSearchCriteria(createVersionCriteria(operator, values)); + } + + public PlatformRequest withVersionIsUnknown(){ + return withVersion(Operator.IS_NULL); + } + + public PlatformRequest withVersionIsKnown(){ + return withVersion(Operator.IS_NOT_NULL); + } + + public SearchCriteria createVersionCriteria(Operator operator, Object... values) { + return createBasicSearchCriteria(Platform.VERSION_PROPERTY, operator, values); + } + + public PlatformRequest withVersionIs(Long version){ + return withVersion(Operator.EQUAL, version); + } + + public PlatformRequest withVersionIsNot(Long version){ + return withVersion(Operator.NOT_EQUAL, version); + } + + public PlatformRequest withVersionIn(Long... version){ + return withVersion(Operator.IN, (Object[])version); + } + + public PlatformRequest withVersionNotIn(Long... version){ + return withVersion(Operator.NOT_IN, (Object[])version); + } + public PlatformRequest withVersionGreaterThan(Long version){ + return withVersion(Operator.GREATER_THAN, version); + } + + public PlatformRequest withVersionGreaterThanOrEqualTo(Long version){ + return withVersion(Operator.GREATER_THAN_OR_EQUAL, version); + } + + public PlatformRequest withVersionLessThan(Long version){ + return withVersion(Operator.LESS_THAN, version); + } + + public PlatformRequest withVersionLessThanOrEqualTo(Long version){ + return withVersion(Operator.LESS_THAN_OR_EQUAL, version); + } + + public PlatformRequest withVersionBetween(Long startOfVersion, Long endOfVersion){ + return withVersion(Operator.BETWEEN, startOfVersion, endOfVersion); + } + + + public PlatformRequest withOrderStatusListMatching(OrderStatusRequest orderStatusRequest){ + return appendSearchCriteria(new SubQuerySearchCriteria(Platform.ID_PROPERTY, orderStatusRequest, OrderStatus.PLATFORM_PROPERTY)); + } + + public PlatformRequest withoutOrderStatusListMatching(OrderStatusRequest orderStatusRequest){ + return appendSearchCriteria(SearchCriteria.not(new SubQuerySearchCriteria(Platform.ID_PROPERTY, orderStatusRequest, OrderStatus.PLATFORM_PROPERTY))); + } + + public PlatformRequest haveOrderStatuses(){ + return withOrderStatusListMatching(Q.orderStatuses().unlimited()); + } + + public PlatformRequest haveNoOrderStatuses(){ + return withoutOrderStatusListMatching(Q.orderStatuses().unlimited()); + } + public PlatformRequest withCustomerListMatching(CustomerRequest customerRequest){ + return appendSearchCriteria(new SubQuerySearchCriteria(Platform.ID_PROPERTY, customerRequest, Customer.PLATFORM_PROPERTY)); + } + + public PlatformRequest withoutCustomerListMatching(CustomerRequest customerRequest){ + return appendSearchCriteria(SearchCriteria.not(new SubQuerySearchCriteria(Platform.ID_PROPERTY, customerRequest, Customer.PLATFORM_PROPERTY))); + } + + public PlatformRequest haveCustomers(){ + return withCustomerListMatching(Q.customers().unlimited()); + } + + public PlatformRequest haveNoCustomers(){ + return withoutCustomerListMatching(Q.customers().unlimited()); + } + + public PlatformRequest count(){ + super.count(); + setPageCountOperation(io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.NAMED_COUNT, "count", null, null)); + return this; + } + public PlatformRequest countAs(String retName){ + super.count(retName); + setPageCountOperation(io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.NAMED_COUNT, "countAs", retName, null)); + return this; + } + public PlatformRequest groupByOrderStatusesWithDetails(OrderStatusRequest subRequest){ + aggregate(Platform.ORDER_STATUS_LIST_PROPERTY, subRequest); + return this; + } + public PlatformRequest groupByCustomersWithDetails(CustomerRequest subRequest){ + aggregate(Platform.CUSTOMER_LIST_PROPERTY, subRequest); + return this; + } + + public PlatformRequest groupById(){ + groupBy(Platform.ID_PROPERTY); + return this; + } + + public PlatformRequest groupByIdAs(String retName){ + groupBy(retName, Platform.ID_PROPERTY); + return this; + } + + public PlatformRequest groupByIdWithFunction(String retName, AggrFunction function){ + groupBy(retName, Platform.ID_PROPERTY, function); + return this; + } + + public PlatformRequest groupByName(){ + groupBy(Platform.NAME_PROPERTY); + return this; + } + + public PlatformRequest groupByNameAs(String retName){ + groupBy(retName, Platform.NAME_PROPERTY); + return this; + } + + public PlatformRequest groupByNameWithFunction(String retName, AggrFunction function){ + groupBy(retName, Platform.NAME_PROPERTY, function); + return this; + } + + public PlatformRequest groupByVersion(){ + groupBy(Platform.VERSION_PROPERTY); + return this; + } + + public PlatformRequest groupByVersionAs(String retName){ + groupBy(retName, Platform.VERSION_PROPERTY); + return this; + } + + public PlatformRequest groupByVersionWithFunction(String retName, AggrFunction function){ + groupBy(retName, Platform.VERSION_PROPERTY, function); + return this; + } + + + + public PlatformRequest orderByIdAscending(){ + addOrderByAscending(Platform.ID_PROPERTY); + return this; + } + + public PlatformRequest orderByIdDescending(){ + addOrderByDescending(Platform.ID_PROPERTY); + return this; + } + + public PlatformRequest orderByNameAscending(){ + addOrderByAscending(Platform.NAME_PROPERTY); + return this; + } + + public PlatformRequest orderByNameDescending(){ + addOrderByDescending(Platform.NAME_PROPERTY); + return this; + } + public PlatformRequest orderByNameAscendingUsingGBK(){ + addOrderByAscendingUsingGBK(Platform.NAME_PROPERTY); + return this; + } + + public PlatformRequest orderByNameDescendingUsingGBK(){ + addOrderByDescendingUsingGBK(Platform.NAME_PROPERTY); + return this; + } + public PlatformRequest orderByVersionAscending(){ + addOrderByAscending(Platform.VERSION_PROPERTY); + return this; + } + + public PlatformRequest orderByVersionDescending(){ + addOrderByDescending(Platform.VERSION_PROPERTY); + return this; + } + + + public PlatformRequest statsFromOrderStatusesAs(String name, OrderStatusRequest subRequest){ + return statsFromOrderStatusesAs(name, subRequest, false); + } + + public PlatformRequest statsFromOrderStatusesAs(String name, OrderStatusRequest subRequest, boolean singleResult){ + addRelationAggregate(name, subRequest, OrderStatus.PLATFORM_PROPERTY, singleResult, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "statsFromOrderStatusesAs", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest statsFromOrderStatuses(OrderStatusRequest subRequest){ + return statsFromOrderStatusesAs(REFINEMENTS, subRequest); + } + public PlatformRequest statsFromCustomersAs(String name, CustomerRequest subRequest){ + return statsFromCustomersAs(name, subRequest, false); + } + + public PlatformRequest statsFromCustomersAs(String name, CustomerRequest subRequest, boolean singleResult){ + addRelationAggregate(name, subRequest, Customer.PLATFORM_PROPERTY, singleResult, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "statsFromCustomersAs", name, Platform.CUSTOMER_LIST_PROPERTY)); + return this; + } + + public PlatformRequest statsFromCustomers(CustomerRequest subRequest){ + return statsFromCustomersAs(REFINEMENTS, subRequest); + } + public PlatformRequest countOrderStatuses(){ + addRelationCount("Count", Q.orderStatuses().unlimited(), OrderStatus.PLATFORM_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countOrderStatuses", null, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest countOrderStatusesAs(String name){ + addRelationCount(name, Q.orderStatuses().unlimited(), OrderStatus.PLATFORM_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countOrderStatusesAs", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest countOrderStatusesWith(String name, OrderStatusRequest subRequest){ + addRelationCount(name, subRequest, OrderStatus.PLATFORM_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countOrderStatusesWith", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + public PlatformRequest countCustomers(){ + addRelationCount("Count", Q.customers().unlimited(), Customer.PLATFORM_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countCustomers", null, Platform.CUSTOMER_LIST_PROPERTY)); + return this; + } + + public PlatformRequest countCustomersAs(String name){ + addRelationCount(name, Q.customers().unlimited(), Customer.PLATFORM_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countCustomersAs", name, Platform.CUSTOMER_LIST_PROPERTY)); + return this; + } + + public PlatformRequest countCustomersWith(String name, CustomerRequest subRequest){ + addRelationCount(name, subRequest, Customer.PLATFORM_PROPERTY, + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.RELATION_COUNT, "countCustomersWith", name, Platform.CUSTOMER_LIST_PROPERTY)); + return this; + } + public PlatformRequest minDisplayOrderOfOrderStatuses(){ + addRelationScalarAggregate("minDisplayOrderOfOrderStatuses", Q.orderStatuses().unlimited(), + OrderStatus.PLATFORM_PROPERTY, OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("min".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "minDisplayOrderOfOrderStatuses", null, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest minDisplayOrderOfOrderStatusesAs(String name){ + return minDisplayOrderOfOrderStatusesAs(name, Q.orderStatuses().unlimited()); + } + + public PlatformRequest minDisplayOrderOfOrderStatusesAs(String name, OrderStatusRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderStatus.PLATFORM_PROPERTY, + OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("min".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "minDisplayOrderOfOrderStatusesAs", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + public PlatformRequest maxDisplayOrderOfOrderStatuses(){ + addRelationScalarAggregate("maxDisplayOrderOfOrderStatuses", Q.orderStatuses().unlimited(), + OrderStatus.PLATFORM_PROPERTY, OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("max".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "maxDisplayOrderOfOrderStatuses", null, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest maxDisplayOrderOfOrderStatusesAs(String name){ + return maxDisplayOrderOfOrderStatusesAs(name, Q.orderStatuses().unlimited()); + } + + public PlatformRequest maxDisplayOrderOfOrderStatusesAs(String name, OrderStatusRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderStatus.PLATFORM_PROPERTY, + OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("max".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "maxDisplayOrderOfOrderStatusesAs", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + public PlatformRequest sumDisplayOrderOfOrderStatuses(){ + addRelationScalarAggregate("sumDisplayOrderOfOrderStatuses", Q.orderStatuses().unlimited(), + OrderStatus.PLATFORM_PROPERTY, OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("sum".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sumDisplayOrderOfOrderStatuses", null, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest sumDisplayOrderOfOrderStatusesAs(String name){ + return sumDisplayOrderOfOrderStatusesAs(name, Q.orderStatuses().unlimited()); + } + + public PlatformRequest sumDisplayOrderOfOrderStatusesAs(String name, OrderStatusRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderStatus.PLATFORM_PROPERTY, + OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("sum".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sumDisplayOrderOfOrderStatusesAs", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + public PlatformRequest avgDisplayOrderOfOrderStatuses(){ + addRelationScalarAggregate("avgDisplayOrderOfOrderStatuses", Q.orderStatuses().unlimited(), + OrderStatus.PLATFORM_PROPERTY, OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("avg".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "avgDisplayOrderOfOrderStatuses", null, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest avgDisplayOrderOfOrderStatusesAs(String name){ + return avgDisplayOrderOfOrderStatusesAs(name, Q.orderStatuses().unlimited()); + } + + public PlatformRequest avgDisplayOrderOfOrderStatusesAs(String name, OrderStatusRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderStatus.PLATFORM_PROPERTY, + OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("avg".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "avgDisplayOrderOfOrderStatusesAs", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + public PlatformRequest standardDeviationDisplayOrderOfOrderStatuses(){ + addRelationScalarAggregate("stdDevDisplayOrderOfOrderStatuses", Q.orderStatuses().unlimited(), + OrderStatus.PLATFORM_PROPERTY, OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "standardDeviationDisplayOrderOfOrderStatuses", null, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest standardDeviationDisplayOrderOfOrderStatusesAs(String name){ + return standardDeviationDisplayOrderOfOrderStatusesAs(name, Q.orderStatuses().unlimited()); + } + + public PlatformRequest standardDeviationDisplayOrderOfOrderStatusesAs(String name, OrderStatusRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderStatus.PLATFORM_PROPERTY, + OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "standardDeviationDisplayOrderOfOrderStatusesAs", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + public PlatformRequest squareRootOfPopulationStandardDeviationDisplayOrderOfOrderStatuses(){ + addRelationScalarAggregate("stdDevPopDisplayOrderOfOrderStatuses", Q.orderStatuses().unlimited(), + OrderStatus.PLATFORM_PROPERTY, OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "squareRootOfPopulationStandardDeviationDisplayOrderOfOrderStatuses", null, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest squareRootOfPopulationStandardDeviationDisplayOrderOfOrderStatusesAs(String name){ + return squareRootOfPopulationStandardDeviationDisplayOrderOfOrderStatusesAs(name, Q.orderStatuses().unlimited()); + } + + public PlatformRequest squareRootOfPopulationStandardDeviationDisplayOrderOfOrderStatusesAs(String name, OrderStatusRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderStatus.PLATFORM_PROPERTY, + OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("stddev_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "squareRootOfPopulationStandardDeviationDisplayOrderOfOrderStatusesAs", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + public PlatformRequest sampleVarianceDisplayOrderOfOrderStatuses(){ + addRelationScalarAggregate("varSampDisplayOrderOfOrderStatuses", Q.orderStatuses().unlimited(), + OrderStatus.PLATFORM_PROPERTY, OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_samp".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sampleVarianceDisplayOrderOfOrderStatuses", null, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest sampleVarianceDisplayOrderOfOrderStatusesAs(String name){ + return sampleVarianceDisplayOrderOfOrderStatusesAs(name, Q.orderStatuses().unlimited()); + } + + public PlatformRequest sampleVarianceDisplayOrderOfOrderStatusesAs(String name, OrderStatusRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderStatus.PLATFORM_PROPERTY, + OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_samp".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "sampleVarianceDisplayOrderOfOrderStatusesAs", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + public PlatformRequest samplePopulationVarianceDisplayOrderOfOrderStatuses(){ + addRelationScalarAggregate("varPopDisplayOrderOfOrderStatuses", Q.orderStatuses().unlimited(), + OrderStatus.PLATFORM_PROPERTY, OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "samplePopulationVarianceDisplayOrderOfOrderStatuses", null, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + public PlatformRequest samplePopulationVarianceDisplayOrderOfOrderStatusesAs(String name){ + return samplePopulationVarianceDisplayOrderOfOrderStatusesAs(name, Q.orderStatuses().unlimited()); + } + + public PlatformRequest samplePopulationVarianceDisplayOrderOfOrderStatusesAs(String name, OrderStatusRequest subRequest){ + addRelationScalarAggregate(name, subRequest, OrderStatus.PLATFORM_PROPERTY, + OrderStatus.DISPLAY_ORDER_PROPERTY, + io.teaql.core.AggrFunction.valueOf("var_pop".toUpperCase(java.util.Locale.ROOT)), + io.teaql.core.QueryOperation.nativeMethod(io.teaql.core.QueryOperation.Kind.AGGREGATE, "samplePopulationVarianceDisplayOrderOfOrderStatusesAs", name, Platform.ORDER_STATUS_LIST_PROPERTY)); + return this; + } + + + + /** + * get topN records + * @param topN records number + */ + public PlatformRequest top(int topN) { + super.top(topN); + return this; + } + + /** Cross-runtime bounded-query alias. */ + public PlatformRequest limit(int limit) { + return top(limit); + } + + /** + * get records from offset(inclusive) to offset+size(exclusive) + * @param offset record offset + * @param size records number + */ + public PlatformRequest offset(int offset, int size) { + super.offset(offset, size); + return this; + } + + /** + * retrieve all records + */ + public PlatformRequest unlimited() { + super.unlimited(); + return this; + } + + /** + * get records of one page + * @param pageNumber page number(1-based) + * @param pageSize page size + */ + public PlatformRequest page(int pageNumber, int pageSize) { + int offset = (pageNumber - 1) * pageSize; + return offset(offset, pageSize); + } + + /** + * get records of one page, default page size is 10 + * @param pageNumber page number(1-based) + */ + public PlatformRequest page(int pageNumber) { + return page(pageNumber, 10); + } +} \ No newline at end of file diff --git a/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/sampledata/SampleDataService.java b/examples/standard-order/generated/lib/src/main/java/com/example/requesttreeorderservice/sampledata/SampleDataService.java new file mode 100644 index 00000000..e69de29b diff --git a/examples/standard-order/generated/lib/src/main/resources/META-INF/services/io.teaql.core.meta.EntityMetaAssembler b/examples/standard-order/generated/lib/src/main/resources/META-INF/services/io.teaql.core.meta.EntityMetaAssembler new file mode 100644 index 00000000..492cc441 --- /dev/null +++ b/examples/standard-order/generated/lib/src/main/resources/META-INF/services/io.teaql.core.meta.EntityMetaAssembler @@ -0,0 +1,2 @@ + +com.example.requesttreeorderservice.EntityMetaRegistry \ No newline at end of file diff --git a/examples/standard-order/models/model.xml b/examples/standard-order/models/model.xml new file mode 100644 index 00000000..1afb6847 --- /dev/null +++ b/examples/standard-order/models/model.xml @@ -0,0 +1,22 @@ + + + + + <_value id="1001" name="New" code="NEW" color="#94A3B8" display_order="1"/> + <_value id="1002" name="Paid" code="PAID" color="#22C55E" display_order="2"/> + <_value id="1003" name="Cancelled" code="CANCELLED" color="#EF4444" display_order="3"/> + + + + + diff --git a/examples/standard-order/pom.xml b/examples/standard-order/pom.xml new file mode 100644 index 00000000..76ccc998 --- /dev/null +++ b/examples/standard-order/pom.xml @@ -0,0 +1,19 @@ + + 4.0.0 + io.teaqlteaql-java-parent + 1.555-RELEASE../../pom.xml + teaql-standard-order-example + 21 + + com.examplerequest-tree-order-service-core0.0.1-SNAPSHOT + io.teaqlteaql-runtime + io.teaqlteaql-sqlite + io.teaqlteaql-provider-jdbc + io.teaqlteaql-query-json + io.teaqlteaql-opentelemetry${project.version}test + junitjunittest + + org.apache.maven.pluginsmaven-surefire-plugin3.2.5 + diff --git a/examples/standard-order/src/test/java/com/example/requesttreeorderservice/StandardOrderGraphStreamTelemetryTest.java b/examples/standard-order/src/test/java/com/example/requesttreeorderservice/StandardOrderGraphStreamTelemetryTest.java new file mode 100644 index 00000000..97e5e222 --- /dev/null +++ b/examples/standard-order/src/test/java/com/example/requesttreeorderservice/StandardOrderGraphStreamTelemetryTest.java @@ -0,0 +1,144 @@ +package com.example.requesttreeorderservice; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import io.teaql.core.InternalIdGenerationService; +import io.teaql.core.UserContext; +import io.teaql.core.meta.SimpleEntityMetaFactory; +import io.teaql.core.sqlite.SqliteDataServiceExecutor; +import io.teaql.provider.jdbc.JdbcSqlExecutor; +import io.teaql.runtime.AppAuditEventSink; +import io.teaql.runtime.DefaultUserContext; +import io.teaql.runtime.SafeAuditEvent; +import io.teaql.runtime.TeaQLRuntime; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import org.junit.Test; +import org.sqlite.SQLiteDataSource; +import static org.junit.Assert.*; + +/** Generated Q/E and audited graph save; no SDK imports or handwritten application SQL. */ +public class StandardOrderGraphStreamTelemetryTest { + static final String PRIVATE = "STANDARD_GRAPH_STREAM_PRIVATE_7a3120c8"; + + @Test public void graphSaveKeepsInheritedAndExplicitEntityCommentsAndIndependentAudit() throws Exception { + try (var fixture = new Fixture()) { + var context = fixture.context; + var platform = Q.platforms().limit(1).comment("load graph root seed") + .purpose("prepare bounded graph fixture").executeForOne(context); + var customer = Q.customers().comment("prepare graph customer").purpose("prepare bounded graph fixture").newEntity(context); + customer.updateName(PRIVATE); customer.updatePlatform(platform); + customer.auditAs("create graph customer"); + var order = Q.customerOrders().comment("prepare graph order").purpose("prepare bounded graph fixture").newEntity(context); + order.updateCustomer(customer); order.updateOrderNumber(PRIVATE); + order.updateOrderDate(LocalDate.of(2026, 10, 12)); order.updateTotalAmount(BigDecimal.valueOf(60)); + order.updateStatusToNew(); order.setComment(null); // Intentionally inherit the graph entry's mutation comment. + var item = Q.orderItems().comment("prepare graph entry").purpose("prepare bounded graph fixture").newEntity(context); + item.updateCustomerOrder(order); item.updateProductName("Controlled graph box"); + item.updateQuantity(3); item.updateUnitPrice(BigDecimal.valueOf(20)); + int before = fixture.audits.size(); + item.auditAs("save bounded order graph").save(context); + var committed = fixture.audits.subList(before, fixture.audits.size()); + assertEquals(3, committed.size()); + assertEquals(java.util.Set.of("Customer", "CustomerOrder", "OrderItem"), + committed.stream().map(SafeAuditEvent::entityType).collect(java.util.stream.Collectors.toSet())); + for (var event : committed) { + var comments = event.traceChain().stream().map(io.teaql.core.TraceNode::getComment).toList(); + assertTrue(comments.contains("save bounded order graph")); + if (event.entityType().equals("Customer")) assertTrue(comments.contains("create graph customer")); + } + var actual = Q.orderItems().withIdIs(item.getId()) + .selectCustomerOrderWith(Q.customerOrders()) + .limit(1).comment("verify persisted bounded graph").purpose("verify graph audit independent of export") + .executeForOne(context); + assertEquals("Controlled graph box", E.orderItem(actual).getProductName().eval()); + var parent = E.orderItem(actual).getCustomerOrder().eval(); + assertEquals(PRIVATE, E.customerOrder(parent).getOrderNumber().eval()); + assertEquals(1, Q.customers().withIdIs(customer.getId()).limit(1) + .comment("verify persisted graph customer").purpose("verify graph audit independent of export").executeForList(context).size()); + assertEquals(before + 3, fixture.audits.size()); assertEquals(0, fixture.ordinaryLogs.get()); + fixture.runtime.getTelemetry().flush(); + } + System.out.println("JAVA_STANDARD_GRAPH PASSED committedEntities=3 inherited=true explicit=true ordinarySqlLogs=0"); + } + + @Test public void streamsRetainRootAndCloseResourcesOnExhaustionAndEarlyClose() throws Exception { + try (var fixture = new Fixture()) { + var context = fixture.context; + var platform = Q.platforms().limit(1).comment("load stream root seed").purpose("prepare bounded stream fixture").executeForOne(context); + var customer = Q.customers().comment("prepare stream customer").purpose("prepare bounded stream fixture").newEntity(context); + customer.updateName("Stream retail"); customer.updatePlatform(platform); customer.auditAs("seed stream customer").save(context); + for (int index = 1; index <= 3; index++) { + var order = Q.customerOrders().comment("prepare stream order").purpose("prepare bounded stream fixture").newEntity(context); + order.updateCustomer(customer); order.updateOrderNumber(PRIVATE + "-" + index); + order.updateOrderDate(LocalDate.of(2026, 10, index)); order.updateTotalAmount(BigDecimal.valueOf(index * 10)); + order.updateStatusToNew(); order.auditAs("seed stream order").save(context); + } + int before = fixture.audits.size(); + List rows; + try (var stream = Q.customerOrdersWithMinimalFields().selectOrderNumber().orderByIdAscending().limit(3) + .comment("stream complete bounded orders").purpose("verify delayed stream completion").executeForStream(context)) { + // The public list executes while a stream is open: its scope must + // not accidentally inherit a leaked stream activation. + assertEquals(1, Q.platformsWithMinimalFields().limit(1).comment("query between stream reads") + .purpose("verify stream activation isolation").executeForList(context).size()); + rows = stream.toList(); + } + assertEquals(3, rows.size()); + for (int index = 0; index < 3; index++) assertEquals(PRIVATE + "-" + (index + 1), E.customerOrder(rows.get(index)).getOrderNumber().eval()); + assertEquals(0, fixture.source.openConnections.get()); + try (var stream = Q.customerOrdersWithMinimalFields().selectOrderNumber().orderByIdAscending().limit(3) + .comment("stream early-close bounded orders").purpose("verify explicit stream cancellation").executeForStream(context)) { + var first = stream.findFirst().orElseThrow(); + assertEquals(PRIVATE + "-1", E.customerOrder(first).getOrderNumber().eval()); + } + assertEquals(0, fixture.source.openConnections.get()); + assertEquals(before, fixture.audits.size()); assertEquals(0, fixture.ordinaryLogs.get()); + fixture.runtime.getTelemetry().flush(); + } + System.out.println("JAVA_STANDARD_STREAM PASSED exhaustedRows=3 cancelledRows=1 connections=0 auditsUnchanged=true ordinarySqlLogs=0"); + } + + private static final class Fixture implements AutoCloseable { + final CountingSource source = new CountingSource(); + final ArrayList audits = new ArrayList<>(); + final AtomicInteger ordinaryLogs = new AtomicInteger(); + final TeaQLRuntime runtime; + final DefaultUserContext context; + Fixture() throws Exception { + source.setUrl("jdbc:sqlite:" + Files.createTempDirectory("teaql-java-standard-graph-stream-").resolve("orders.sqlite")); + var service = new SqliteDataServiceExecutor("sqlite", new JdbcSqlExecutor(source), source); + var ids = new AtomicLong(4000); + runtime = TeaQLRuntime.builder().metadata(new SimpleEntityMetaFactory()) + .dataService("default", service).dataService("sqlite", service) + .idGenerationService(new InternalIdGenerationService() { + public Long generateId(UserContext caller, io.teaql.core.Entity entity) { return ids.incrementAndGet(); } + public void ensureFloor(String type, long floor) { ids.accumulateAndGet(floor, Math::max); } + }).queryExecutionLogging(false).mutationExecutionLogging(false) + .logSink((caller, metadata) -> ordinaryLogs.incrementAndGet()).build().install(GeneratedRuntimeModule.module()); + context = new DefaultUserContext(runtime); + context.putAttribute(AppAuditEventSink.class.getName(), (AppAuditEventSink) (caller, event) -> audits.add(event)); + context.ensureSchema(); assertEquals(4, audits.size()); context.ensureSchema(); assertEquals(4, audits.size()); + } + public void close() { runtime.close(); } + } + + /** Infrastructure observation only: driver calls and SQL remain runtime-owned. */ + private static final class CountingSource extends SQLiteDataSource { + final AtomicInteger openConnections = new AtomicInteger(); + @Override public java.sql.Connection getConnection() throws java.sql.SQLException { + var connection = super.getConnection(); openConnections.incrementAndGet(); + var closed = new java.util.concurrent.atomic.AtomicBoolean(); + return (java.sql.Connection) java.lang.reflect.Proxy.newProxyInstance(java.sql.Connection.class.getClassLoader(), + new Class[]{java.sql.Connection.class}, (proxy, method, args) -> { + try { return method.invoke(connection, args); } + catch (java.lang.reflect.InvocationTargetException failure) { throw failure.getCause(); } + finally { if (method.getName().equals("close") && closed.compareAndSet(false, true)) openConnections.decrementAndGet(); } + }); + } + } +} diff --git a/examples/standard-order/src/test/java/com/example/requesttreeorderservice/StandardOrderTelemetryTest.java b/examples/standard-order/src/test/java/com/example/requesttreeorderservice/StandardOrderTelemetryTest.java new file mode 100644 index 00000000..ef9930c3 --- /dev/null +++ b/examples/standard-order/src/test/java/com/example/requesttreeorderservice/StandardOrderTelemetryTest.java @@ -0,0 +1,123 @@ +package com.example.requesttreeorderservice; + +import com.example.requesttreeorderservice.customerorder.CustomerOrder; +import com.example.requesttreeorderservice.orderstatus.OrderStatus; +import io.teaql.core.InternalIdGenerationService; +import io.teaql.core.SmartList; +import io.teaql.core.UserContext; +import io.teaql.core.meta.SimpleEntityMetaFactory; +import io.teaql.core.sqlite.SqliteDataServiceExecutor; +import io.teaql.provider.jdbc.JdbcSqlExecutor; +import io.teaql.query.json.JsonRequests; +import io.teaql.runtime.AppAuditEventSink; +import io.teaql.runtime.DefaultUserContext; +import io.teaql.runtime.SafeAuditEvent; +import io.teaql.runtime.TeaQLRuntime; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import org.junit.Test; +import org.sqlite.SQLiteDataSource; +import static org.junit.Assert.*; + +/** Ordinary generated business code: no SDK setup, manual span or application SQL. */ +public class StandardOrderTelemetryTest { + private static final String PRIVATE = "STANDARD_PRIVATE_ORDER_79463A12"; + + @Test public void completeOrderPagesKeepCountsFacetsRelationsAndAutomaticDiagnostics() throws Exception { + var source = new SQLiteDataSource(); + source.setUrl("jdbc:sqlite:" + Files.createTempDirectory("teaql-java-standard-order-").resolve("orders.sqlite")); + var service = new SqliteDataServiceExecutor("sqlite", new JdbcSqlExecutor(source), source); + var ids = new AtomicLong(2000); + var ordinaryLogs = new AtomicInteger(); + var audits = new ArrayList(); + try (var runtime = TeaQLRuntime.builder().metadata(new SimpleEntityMetaFactory()) + .dataService("default", service).dataService("sqlite", service) + .idGenerationService(new InternalIdGenerationService() { + @Override public Long generateId(UserContext context, io.teaql.core.Entity entity) { return ids.incrementAndGet(); } + @Override public void ensureFloor(String type, long floor) { ids.accumulateAndGet(floor, Math::max); } + }).queryExecutionLogging(false).mutationExecutionLogging(false) + .logSink((context, metadata) -> ordinaryLogs.incrementAndGet()).build() + .install(GeneratedRuntimeModule.module())) { + var context = new DefaultUserContext(runtime); + context.putAttribute(AppAuditEventSink.class.getName(), (AppAuditEventSink) (caller, event) -> audits.add(event)); + context.ensureSchema(); + assertEquals(4, audits.size()); + context.ensureSchema(); + assertEquals("repeat seed must not create another audit", 4, audits.size()); + var platform = Q.platforms().limit(1).comment("load seeded commerce root") + .purpose("prepare the operational queue").executeForOne(context); + var customer = Q.customers().comment("create fixture customer").purpose("prepare order graph").newEntity(context); + customer.updateName("Controlled retail"); customer.updatePlatform(platform); + customer.auditAs("seed operational queue customer").save(context); + for (int index = 1; index <= 4; index++) { + var order = Q.customerOrders().comment("create fixture order").purpose("prepare order graph").newEntity(context); + order.updateCustomer(customer); + order.updateOrderNumber(index < 4 ? PRIVATE + "-00" + index : "OTHER-004"); + order.updateOrderDate(LocalDate.of(2026, 10, index)); + order.updateTotalAmount(BigDecimal.valueOf(index < 4 ? index * 100 : 900)); + if (index == 1) order.updateStatusToNew(); else order.updateStatusToPaid(); + order.auditAs("seed operational queue order").save(context); + for (int item = 1; item <= 5; item++) { + var line = Q.orderItems().comment("create fixture item").purpose("prepare bounded item previews").newEntity(context); + line.updateCustomerOrder(order); line.updateProductName("Box " + item); + line.updateQuantity(item); line.updateUnitPrice(BigDecimal.valueOf(20)); + line.auditAs("seed operational queue item").save(context); + } + } + int beforeQueries = audits.size(); + for (int repetition = 1; repetition <= 2; repetition++) verifyPage(fullOrderPage(context)); + assertEquals("queries must not create committed audit events", beforeQueries, audits.size()); + assertEquals("ordinary SQL output stays independently disabled", 0, ordinaryLogs.get()); + runtime.getTelemetry().flush(); + } + System.out.println("JAVA_STANDARD_ORDER PASSED pages=2 total=3 rows=1 preview=3 itemCount=5 facets=3 seeds=4 repeatedSeedWrites=0 ordinarySqlLogs=0"); + } + + private static SmartList fullOrderPage(UserContext context) { + return JsonRequests.findWithJson(context, Q.customerOrdersWithMinimalFields(), "{\"orderNumber\":\"" + PRIVATE + "\"}") + .countAs("totalOrders").facetByStatusAs("statusCounts", statusFacets()) + .selectOrderNumber().selectOrderDate().selectStatusWith(statusDetails()) + .selectOrderItemListWith(itemPreviews()).countOrderItemsAs("itemCount") + .orderByOrderDateDescending().orderByIdDescending().offset(1, 1) + .comment("load order list").purpose("render order list page").executeForList(context); + } + private static com.example.requesttreeorderservice.orderstatus.OrderStatusRequest statusDetails() { + return Q.orderStatusesWithMinimalFields().selectName().selectColor().comment("load status"); + } + private static com.example.requesttreeorderservice.orderitem.OrderItemRequest itemPreviews() { + return Q.orderItemsWithMinimalFields().selectProductName().selectQuantity() + .orderByIdAscending().limit(3).comment("load item previews"); + } + private static com.example.requesttreeorderservice.orderstatus.OrderStatusRequest statusFacets() { + return Q.orderStatusesWithMinimalFields().selectName().selectColor().countAs("orderCount") + .sumTotalAmountOfCustomerOrdersAs("orderTotalAmount") + .orderByDisplayOrderAscending().comment("load status facets"); + } + private static void verifyPage(SmartList rows) { + assertEquals(1, rows.size()); assertEquals(3, rows.getTotalCount()); + assertEquals(3, ((Number) rows.aggregationNumberProperties().get("totalOrders")).intValue()); + var row = rows.first(); + assertEquals(PRIVATE + "-002", E.customerOrder(row).getOrderNumber().eval()); + assertEquals(LocalDate.of(2026, 10, 2), E.customerOrder(row).getOrderDate().eval()); + assertEquals("Paid", E.customerOrder(row).getStatus().getName().eval()); + assertEquals(Integer.valueOf(3), E.customerOrder(row).getOrderItemList().size().eval()); + assertEquals("Box 1", E.customerOrder(row).getOrderItemList().first().getProductName().eval()); + assertEquals(5, ((Number) row.getDynamicProperty("itemCount")).intValue()); + var facets = rows.getFacet("statusCounts"); assertEquals(3, facets.size()); + int[] counts = {1, 2, 0}, sums = {100, 500, 0}; + String[] names = {"New", "Paid", "Cancelled"}; + for (int index = 0; index < 3; index++) { + var facet = (OrderStatus) facets.get(index); + assertEquals(names[index], E.orderStatus(facet).getName().eval()); + assertEquals(counts[index], ((Number) facet.getDynamicProperty("orderCount")).intValue()); + assertEquals(0, new BigDecimal(facet.getDynamicProperty("orderTotalAmount").toString()).compareTo(BigDecimal.valueOf(sums[index]))); + } + var unloaded = assertThrows(RuntimeException.class, () -> E.customerOrder(row).getTotalAmount().eval()); + assertTrue("an omitted amount must be NotLoaded, not null", unloaded.getClass().getSimpleName().contains("NotLoaded")); + } +} diff --git a/examples/standard-order/verify.sh b/examples/standard-order/verify.sh new file mode 100644 index 00000000..20f98569 --- /dev/null +++ b/examples/standard-order/verify.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail +example="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo="$(cd "$example/../.." && pwd)" +evidence="$(mktemp -d /tmp/teaql-java-standard-order-evidence-XXXXXX)" +hash_library() { + (cd "$example" && find generated/lib -type f ! -path '*/target/*' -print0 | sort -z | xargs -0 sha256sum) +} +hash_library > "$evidence/generated-before.sha256" +for round in first second; do + timeout --kill-after=10s 180s env -u TEAQL_TELEMETRY_ENABLED -u TEAQL_TELEMETRY_SERVICE_NAME \ + -u TEAQL_TELEMETRY_OTLP_ENDPOINT mvn -q -f "$repo/pom.xml" -Pruntime-examples \ + -pl examples/standard-order -am -Dtest=StandardOrderTelemetryTest,StandardOrderGraphStreamTelemetryTest \ + -Dsurefire.failIfNoSpecifiedTests=false test 2>&1 | tee "$evidence/$round.log" + rg -Fq 'JAVA_STANDARD_ORDER PASSED' "$evidence/$round.log" + rg -Fq 'JAVA_STANDARD_GRAPH PASSED' "$evidence/$round.log" + rg -Fq 'JAVA_STANDARD_STREAM PASSED' "$evidence/$round.log" + for name in TEAQL_TELEMETRY_ENABLED TEAQL_TELEMETRY_SERVICE_NAME TEAQL_TELEMETRY_OTLP_ENDPOINT; do + rg -Fq "$name" "$evidence/$round.log" + done +done +hash_library > "$evidence/generated-after.sha256" +cmp "$evidence/generated-before.sha256" "$evidence/generated-after.sha256" +echo "PASS Java standard Order example; evidence retained: $evidence" diff --git a/examples/trace-chain/lib/pom.xml b/examples/trace-chain/lib/pom.xml index 9ef081b1..fb678b17 100644 --- a/examples/trace-chain/lib/pom.xml +++ b/examples/trace-chain/lib/pom.xml @@ -12,7 +12,7 @@ 21 4.1.1 - 1.554-RELEASE + 1.555-RELEASE UTF-8 true @@ -67,7 +67,7 @@ io.teaql teaql-sqlite - 1.554-RELEASE + 1.555-RELEASE io.teaql diff --git a/examples/trace-chain/pom.xml b/examples/trace-chain/pom.xml index b7958ec9..d7b2f1c7 100644 --- a/examples/trace-chain/pom.xml +++ b/examples/trace-chain/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../../pom.xml teaql-trace-chain-example @@ -17,6 +17,7 @@ io.teaqlteaql-runtime io.teaqlteaql-sqlite io.teaqlteaql-provider-jdbc + io.teaqlteaql-opentelemetry${project.version}test junitjunittest diff --git a/examples/trace-chain/src/test/java/io/teaql/examples/tracechain/GeneratedStandardTelemetryExampleTest.java b/examples/trace-chain/src/test/java/io/teaql/examples/tracechain/GeneratedStandardTelemetryExampleTest.java new file mode 100644 index 00000000..516936e5 --- /dev/null +++ b/examples/trace-chain/src/test/java/io/teaql/examples/tracechain/GeneratedStandardTelemetryExampleTest.java @@ -0,0 +1,51 @@ +package io.teaql.examples.tracechain; + +import com.teaql.tracechainservice.E; +import com.teaql.tracechainservice.Q; +import com.teaql.tracechainservice.GeneratedRuntimeModule; +import io.teaql.core.meta.SimpleEntityMetaFactory; +import io.teaql.core.sql.portable.IdSpaceIdGenerator; +import io.teaql.core.sqlite.SqliteDataServiceExecutor; +import io.teaql.provider.jdbc.JdbcSqlExecutor; +import io.teaql.runtime.*; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Test; +import org.sqlite.SQLiteDataSource; +import static org.junit.Assert.*; + +/** Generated Q/E + automatic bootstrap, with no application SDK setup or tracing code. */ +public class GeneratedStandardTelemetryExampleTest { + @Test public void startupAndRepeatedBootstrapWithOrdinarySqlLoggingDisabled() throws Exception { + var source = new SQLiteDataSource(); + source.setUrl("jdbc:sqlite:" + Files.createTempDirectory("teaql-java-standard-telemetry-").resolve("example.db")); + var driver = new JdbcSqlExecutor(source); + var provider = new SqliteDataServiceExecutor("sqlite", driver, source); + var ordinaryLogs = new AtomicInteger(); + var audits = new ArrayList(); + try (var runtime = TeaQLRuntime.builder().metadata(new SimpleEntityMetaFactory()) + .dataService("default", provider).dataService("sqlite", provider) + .idGenerationService(new IdSpaceIdGenerator(new IdDatabase(driver))) + .queryExecutionLogging(false).mutationExecutionLogging(false) + .logSink((caller, metadata) -> ordinaryLogs.incrementAndGet()).build() + .install(GeneratedRuntimeModule.module())) { + var context = new DefaultUserContext(runtime); + context.putAttribute(AppAuditEventSink.class.getName(), (AppAuditEventSink) (caller, event) -> audits.add(event)); + context.ensureSchema(); + assertEquals("one generated root seed must commit", 1, audits.size()); + context.ensureSchema(); + assertEquals("repeated bootstrap must not write or produce another committed audit", 1, audits.size()); + for (int iteration = 0; iteration < 2; iteration++) { + var rows = Q.platformsWithMinimalFields().withIdIs(1L).orderByIdAscending().limit(1) + .comment("load seeded platform").purpose("verify automatic standard telemetry") + .executeForList(context); + assertEquals(1, rows.size()); + assertEquals(Long.valueOf(1), E.platform(rows.get(0)).getId().eval()); + } + assertEquals("ordinary logging remains separately disabled", 0, ordinaryLogs.get()); + runtime.getTelemetry().flush(); + } + System.out.println("JAVA_STANDARD_TELEMETRY_EXAMPLE PASSED ensures=2 committedSeeds=1 queries=2 rows=1 ordinarySqlLogs=0"); + } +} diff --git a/examples/trace-chain/verify-standard-telemetry.sh b/examples/trace-chain/verify-standard-telemetry.sh new file mode 100755 index 00000000..1d2b3b39 --- /dev/null +++ b/examples/trace-chain/verify-standard-telemetry.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +example_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_dir="$(cd "$example_dir/../.." && pwd)" +run_dir="$(mktemp -d -t teaql-java-standard-telemetry.XXXXXXXX)" +names=(TEAQL_TELEMETRY_ENABLED TEAQL_TELEMETRY_SERVICE_NAME TEAQL_TELEMETRY_OTLP_ENDPOINT) +disabled=(env -u "${names[0]}" -u "${names[1]}" -u "${names[2]}") +args=(-B -f "$repo_dir/pom.xml" -Pruntime-examples -pl examples/trace-chain -am + -Dtest=GeneratedStandardTelemetryExampleTest -Dsurefire.failIfNoSpecifiedTests=false test) + +library_manifest() { + (cd "$example_dir" && find lib -type f ! -path '*/target/*' -print0 | + sort -z | xargs -0 sha256sum) +} +library_manifest > "$run_dir/library-before.sha256" +for repetition in 1 2; do + "${disabled[@]}" mvn "${args[@]}" > "$run_dir/disabled-$repetition.log" 2>&1 + grep -Fq 'JAVA_STANDARD_TELEMETRY_EXAMPLE PASSED' "$run_dir/disabled-$repetition.log" + grep -Fq 'TeaQL telemetry: supported, disabled' "$run_dir/disabled-$repetition.log" + for name in "${names[@]}"; do grep -Fq "$name" "$run_dir/disabled-$repetition.log"; done +done +if [[ ${TEAQL_TELEMETRY_ENABLED:-false} == true ]]; then + for repetition in 1 2; do + mvn "${args[@]}" > "$run_dir/enabled-$repetition.log" 2>&1 + grep -Fq 'JAVA_STANDARD_TELEMETRY_EXAMPLE PASSED' "$run_dir/enabled-$repetition.log" + grep -Fq 'TeaQL telemetry: enabled' "$run_dir/enabled-$repetition.log" + done +fi +library_manifest > "$run_dir/library-after.sha256" +cmp "$run_dir/library-before.sha256" "$run_dir/library-after.sha256" +printf 'PASS Java standard telemetry example; evidence=%s\n' "$run_dir" diff --git a/pom.xml b/pom.xml index 940e8791..ed279f16 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE pom teaql-java-parent @@ -374,6 +374,8 @@ examples/school-management examples/trace-chain/lib examples/trace-chain + examples/standard-order/generated/lib + examples/standard-order diff --git a/scripts/verify-examples.sh b/scripts/verify-examples.sh index 11b1d852..d5d15aca 100755 --- a/scripts/verify-examples.sh +++ b/scripts/verify-examples.sh @@ -4,7 +4,7 @@ set -euo pipefail repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" verification_dir="$(mktemp -d)" trap 'rm -rf -- "$verification_dir"' EXIT -expected=(business-id-runtime conformance order-management round-trip-reference-runtime school-management security-foundations shared-load-state trace-chain) +expected=(business-id-runtime conformance order-management round-trip-reference-runtime school-management security-foundations shared-load-state standard-order trace-chain) mapfile -t actual < <(find "$repo/examples" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort) if [[ "${actual[*]}" != "${expected[*]}" ]]; then echo "example inventory changed; update scripts/verify-examples.sh: ${actual[*]}" >&2 @@ -32,4 +32,6 @@ mvn -q -f examples/order-management/pom.xml install -DskipTests # the developer's retained database while verifying the runtime checkout. (cd "$verification_dir" && mvn -q -f "$repo/examples/order-management/pom.xml" exec:java -pl java-app-console) bash examples/trace-chain/verify.sh +bash examples/trace-chain/verify-standard-telemetry.sh +bash examples/standard-order/verify.sh echo "PASS: standard Java examples; shared-load-state has its own generator-backed verify.sh gate" diff --git a/teaql-android/pom.xml b/teaql-android/pom.xml index 2f4e456f..ae06ce6f 100644 --- a/teaql-android/pom.xml +++ b/teaql-android/pom.xml @@ -7,7 +7,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-android diff --git a/teaql-business-id-jdbc/pom.xml b/teaql-business-id-jdbc/pom.xml index 134e21fd..63bd27ff 100644 --- a/teaql-business-id-jdbc/pom.xml +++ b/teaql-business-id-jdbc/pom.xml @@ -7,7 +7,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-cloud/pom.xml b/teaql-cloud/pom.xml index 5d4a9f9e..09c69422 100644 --- a/teaql-cloud/pom.xml +++ b/teaql-cloud/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-cloud teaql-cloud diff --git a/teaql-context-runtime-tools/pom.xml b/teaql-context-runtime-tools/pom.xml index fcd36ea7..ea0f45a8 100644 --- a/teaql-context-runtime-tools/pom.xml +++ b/teaql-context-runtime-tools/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-context-runtime-tools diff --git a/teaql-core/pom.xml b/teaql-core/pom.xml index 5a118b3b..161317bc 100644 --- a/teaql-core/pom.xml +++ b/teaql-core/pom.xml @@ -7,7 +7,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-core/src/main/java/io/teaql/core/BaseRequest.java b/teaql-core/src/main/java/io/teaql/core/BaseRequest.java index 98abe986..896ab5fd 100644 --- a/teaql-core/src/main/java/io/teaql/core/BaseRequest.java +++ b/teaql-core/src/main/java/io/teaql/core/BaseRequest.java @@ -31,6 +31,13 @@ public abstract class BaseRequest implements SearchRequest protected String comment; protected String purpose; + protected Map queryOperations = new HashMap<>(); + protected QueryOperation pageCountOperation; + @Override public Map queryOperations() { return Map.copyOf(queryOperations); } + @Override public QueryOperation pageCountOperation() { return pageCountOperation; } + @FrameworkInternal("Generator supplies the exact native count method and alias") + @com.fasterxml.jackson.annotation.JsonIgnore + public void setPageCountOperation(QueryOperation operation) { pageCountOperation = operation; } protected DynamicPropertyMetadata dynamicPropertyMetadata; @Override public DynamicPropertyMetadata getDynamicPropertyMetadata() { return dynamicPropertyMetadata; } @@ -212,6 +219,11 @@ public void enhanceRelation(String propertyName, SearchRequest request) { this.enhanceRelations.put(propertyName, request); } + public void enhanceRelation(String propertyName, SearchRequest request, QueryOperation operation) { + enhanceRelation(propertyName, request); + queryOperations.put(propertyName, operation); + } + @Override public List getProjections() { return projections; @@ -496,6 +508,32 @@ public void addAggregateDynamicProperty(String name, SearchRequest subRequest) { this.addAggregateDynamicProperty(name, subRequest, false); } + @FrameworkInternal("Generator passes relation metadata and native operation, not trace text") + public void addRelationAggregate(String name, SearchRequest subRequest, String partition, + boolean singleResult, QueryOperation operation) { + var snapshot = new QueryRequestSnapshot(subRequest, subRequest.requestScope()); + snapshot.setPartitionProperty(partition); + addAggregateDynamicProperty(name, snapshot, singleResult); + dynamicAggregateAttributes.get(dynamicAggregateAttributes.size() - 1).setOperation(operation); + } + + @FrameworkInternal("Relation count copies the child template before changing aggregates") + public void addRelationCount(String name, SearchRequest subRequest, String partition, QueryOperation operation) { + var snapshot = new QueryRequestSnapshot(subRequest, subRequest.requestScope()); + snapshot.setAggregations(new Aggregations()); + snapshot.count(name); + addRelationAggregate(name, snapshot, partition, true, operation); + } + + @FrameworkInternal("Scalar relation aggregates copy a child template before changing its AST") + public void addRelationScalarAggregate(String name, SearchRequest subRequest, String partition, + String field, AggrFunction function, QueryOperation operation) { + var snapshot = new QueryRequestSnapshot(subRequest, subRequest.requestScope()); + snapshot.setAggregations(new Aggregations()); + snapshot.addAggregate(name, field, function); + addRelationAggregate(name, snapshot, partition, true, operation); + } + public void addSingleAggregateDynamicProperty(String name, SearchRequest subRequest) { this.addAggregateDynamicProperty(name, subRequest, true); } @@ -642,24 +680,28 @@ public void addAggregate(String retName, String propertyName, AggrFunction funct public BaseRequest count() { countProperty(TeaQLConstants.ROOT_LIST_PARAMETER_NAME, BaseEntity.ID_PROPERTY); + setPageCountOperation(QueryOperation.nativeMethod(QueryOperation.Kind.NAMED_COUNT, "count", null, null)); return this; } public BaseRequest count(String retName) { countProperty(retName, BaseEntity.ID_PROPERTY); + setPageCountOperation(QueryOperation.nativeMethod(QueryOperation.Kind.NAMED_COUNT, "count", retName, null)); return this; } /** Framework-owned count snapshot created after trusted policy enforcement. */ public SearchRequest internalCountRequest() { + return internalCountRequest(TeaQLConstants.ROOT_LIST_PARAMETER_NAME); + } + public SearchRequest internalCountRequest(String alias) { TempRequest countRequest = new TempRequest(returnType, getTypeName(), entityFactory); propagateMetadata(countRequest); countRequest.comment = comment; countRequest.purpose = purpose; countRequest.searchCriteria = searchCriteria; - countRequest.partitionProperty = partitionProperty; countRequest.extensions.putAll(extensions); - countRequest.count(); + countRequest.count(alias); return countRequest; } @@ -771,7 +813,7 @@ private EntityMetaFactory metadataFactory() { return boundMetadata != null ? boundMetadata : EntityMetaFactory.get(); } - private void propagateMetadata(BaseRequest request) { + protected void propagateMetadata(BaseRequest request) { if (boundMetadata != null) { request.bindMetadata(boundMetadata); } @@ -1015,6 +1057,13 @@ public BaseRequest addFacet(String facetName, String relationName, SearchRequest return this; } + public BaseRequest addFacet(String facetName, String relationName, SearchRequest request, + boolean includeAllFacets, QueryOperation operation) { + addFacet(facetName, relationName, request, includeAllFacets); + facetRequests.get(facetRequests.size() - 1).setOperation(operation); + return this; + } + @Override public List getFacetRequests() { return facetRequests; diff --git a/teaql-core/src/main/java/io/teaql/core/ExecutableRequest.java b/teaql-core/src/main/java/io/teaql/core/ExecutableRequest.java index 4c9ae4ea..cdcce725 100644 --- a/teaql-core/src/main/java/io/teaql/core/ExecutableRequest.java +++ b/teaql-core/src/main/java/io/teaql/core/ExecutableRequest.java @@ -38,22 +38,6 @@ public SmartList executeForList(UserContext context) { return context.executeForList(this); } - /** - * Executes a stable offset page and attaches the exact filtered total to - * the returned SmartList. Generated request filters and trusted runtime - * policy are shared by the rows and count aggregation. - */ - public SmartList executeForPage(UserContext context, int offset, int limit) { - if (offset < 0) { - throw new IllegalArgumentException("offset must not be negative"); - } - if (limit < 1 || limit > SearchRequest.DEFAULT_HARD_LIMIT) { - throw new IllegalArgumentException( - "limit must be between 1 and " + SearchRequest.DEFAULT_HARD_LIMIT); - } - return context.executeForPage(this, offset, limit); - } - public T executeForOne(UserContext context) { return context.executeForOne(this); } diff --git a/teaql-core/src/main/java/io/teaql/core/ExecutionMetadata.java b/teaql-core/src/main/java/io/teaql/core/ExecutionMetadata.java index a1634559..243d8566 100644 --- a/teaql-core/src/main/java/io/teaql/core/ExecutionMetadata.java +++ b/teaql-core/src/main/java/io/teaql/core/ExecutionMetadata.java @@ -17,6 +17,7 @@ public final class ExecutionMetadata { private List parameters = List.of(); private String debugQuery; private List traceChain; + private List requestTraceChain = List.of(); private List mutationLineage = List.of(); private String statementOperation; private String comment; @@ -29,6 +30,12 @@ public final class ExecutionMetadata { private String sqlOmissionReason; private String executionOutcome; private String batchOutcome; + private transient SqlExecutionCorrelation sqlExecutionCorrelation; + @FrameworkInternal("Statement-owned telemetry identity") + @com.fasterxml.jackson.annotation.JsonIgnore + public SqlExecutionCorrelation getSqlExecutionCorrelation() { return sqlExecutionCorrelation; } + @FrameworkInternal("SQL provider observation only") + public void setSqlExecutionCorrelation(SqlExecutionCorrelation correlation) { sqlExecutionCorrelation = correlation; } @com.fasterxml.jackson.annotation.JsonIgnore private transient SqlIntentRedactions intentRedactions; @@ -99,9 +106,19 @@ public void setParameters(List parameters) { public String getDebugQuery() { return debugQuery; } public void setDebugQuery(String debugQuery) { this.debugQuery = debugQuery; } + @com.fasterxml.jackson.annotation.JsonIgnore public List getTraceChain() { return traceChain; } + @com.fasterxml.jackson.annotation.JsonIgnore public void setTraceChain(List traceChain) { this.traceChain = traceChain == null ? List.of() : List.copyOf(traceChain); } + /** Business ancestry. Legacy getTraceChain is the physical path, not this chain. */ + @com.fasterxml.jackson.annotation.JsonProperty("traceChain") + public List getRequestTraceChain() { return requestTraceChain; } + @com.fasterxml.jackson.annotation.JsonProperty("traceChain") + public void setRequestTraceChain(List trace) { requestTraceChain = trace == null ? List.of() : List.copyOf(trace); } + @com.fasterxml.jackson.annotation.JsonProperty("tracePath") + public List getTracePath() { return traceChain == null ? List.of() : traceChain; } + public List getMutationLineage() { return mutationLineage; } public void setMutationLineage(List lineage) { mutationLineage = lineage == null ? List.of() : List.copyOf(lineage); diff --git a/teaql-core/src/main/java/io/teaql/core/FacetRequest.java b/teaql-core/src/main/java/io/teaql/core/FacetRequest.java index a0c8a699..88233751 100644 --- a/teaql-core/src/main/java/io/teaql/core/FacetRequest.java +++ b/teaql-core/src/main/java/io/teaql/core/FacetRequest.java @@ -14,6 +14,11 @@ public class FacetRequest { * the facetName, like "facetOfStatus" */ private String facetName; + private QueryOperation operation; + @com.fasterxml.jackson.annotation.JsonIgnore + public QueryOperation getOperation() { return operation; } + @com.fasterxml.jackson.annotation.JsonIgnore + public void setOperation(QueryOperation operation) { this.operation = operation; } /** * the relation name for facet, `status` in the example diff --git a/teaql-core/src/main/java/io/teaql/core/QueryOperation.java b/teaql-core/src/main/java/io/teaql/core/QueryOperation.java new file mode 100644 index 00000000..4d1c3bc1 --- /dev/null +++ b/teaql-core/src/main/java/io/teaql/core/QueryOperation.java @@ -0,0 +1,46 @@ +package io.teaql.core; + +/** Generator-owned native Q spelling; never reconstructed from SQL or a relation name. */ +public record QueryOperation(Kind kind, String method, String alias, String relation) { + public enum Kind { NAMED_COUNT, FACET, RELATION_COUNT, RELATION_SELECT, AGGREGATE, PAGINATION } + + public QueryOperation { + java.util.Objects.requireNonNull(kind, "kind"); + if (method != null && !method.matches("[A-Za-z_][A-Za-z_0-9]*")) + throw new IllegalArgumentException("QUERY_OPERATION_INVALID: native method must be an identifier"); + } + public static QueryOperation nativeMethod(Kind kind, String method, String alias, String relation) { + return new QueryOperation(kind, java.util.Objects.requireNonNull(method), alias, relation); + } + public static QueryOperation framework(Kind kind, String alias, String relation) { + return new QueryOperation(kind, null, alias, relation); + } + public String comment() { + if (method != null) return method + "(" + (alias != null ? stringLiteral(alias) + : kind == Kind.RELATION_SELECT ? "..." : "") + ")"; + return "framework: " + kind.name().toLowerCase(java.util.Locale.ROOT).replace('_', '-') + + (relation == null ? "" : " " + relation) + + (alias == null ? "" : " " + stringLiteral(alias)); + } + /** One renderer for Java Q string literals, independent of SQL quoting. */ + public static String stringLiteral(String text) { + StringBuilder out = new StringBuilder("\""); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + switch (c) { + case '\\' -> out.append("\\\\"); + case '"' -> out.append("\\\""); + case '\n' -> out.append("\\n"); + case '\r' -> out.append("\\r"); + case '\t' -> out.append("\\t"); + case '\b' -> out.append("\\b"); + case '\f' -> out.append("\\f"); + default -> { + if (c < 32 || c == 127) out.append(String.format("\\u%04x", (int) c)); + else out.append(c); + } + } + } + return out.append('"').toString(); + } +} diff --git a/teaql-core/src/main/java/io/teaql/core/QueryRequest.java b/teaql-core/src/main/java/io/teaql/core/QueryRequest.java index 89c167c7..29492eef 100644 --- a/teaql-core/src/main/java/io/teaql/core/QueryRequest.java +++ b/teaql-core/src/main/java/io/teaql/core/QueryRequest.java @@ -2,6 +2,11 @@ public interface QueryRequest { QueryIntent intent(); + QueryRequestScope requestScope(); + default QueryRequestScope parentRequest() { return requestScope().parentRequest(); } + default QueryRequestScope rootRequest() { return requestScope().rootRequest(); } + default String localComment() { return requestScope().comment(); } + default java.util.List businessTraceChain() { return requestScope().traceChain(); } default String comment() { return intent().comment(); } default String purpose() { return intent().purpose(); } } diff --git a/teaql-core/src/main/java/io/teaql/core/QueryRequestScope.java b/teaql-core/src/main/java/io/teaql/core/QueryRequestScope.java new file mode 100644 index 00000000..9fc4d894 --- /dev/null +++ b/teaql-core/src/main/java/io/teaql/core/QueryRequestScope.java @@ -0,0 +1,62 @@ +package io.teaql.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** Immutable, execution-owned header. It contains no AST, Context or mutation state. */ +public final class QueryRequestScope { + private final QueryRequestScope parent; + private final String name; + private final String comment; + private final QueryIntent intent; + private final QueryOperation operation; + + private QueryRequestScope(QueryRequestScope parent, String name, String comment, + QueryIntent intent, QueryOperation operation) { + this.parent = parent; + this.name = java.util.Objects.requireNonNull(name); + this.comment = comment; + this.intent = java.util.Objects.requireNonNull(intent); + this.operation = operation; + } + public static QueryRequestScope root(String name, QueryIntent intent) { + return new QueryRequestScope(null, name, intent.comment(), intent, null); + } + public static QueryRequestScope capture(SearchRequest request) { + return request.requestScope() != null ? request.requestScope() + : root(request.getTypeName(), request.inheritedQueryIntent() != null + ? request.inheritedQueryIntent() : QueryIntent.of(request.comment(), request.purpose())); + } + public QueryRequestScope parentRequest() { return parent; } + public QueryRequestScope rootRequest() { + QueryRequestScope result = this; + while (result.parent != null) result = result.parent; + return result; + } + public String requestName() { return name; } + public String comment() { return comment; } + public String purpose() { return intent.purpose(); } + public QueryIntent intent() { return intent; } + public QueryOperation operation() { return operation; } + public QueryRequestScope deriveOperation(String name, QueryOperation operation) { + return new QueryRequestScope(this, name, operation.comment(), intent, operation); + } + public QueryRequestScope deriveHelper(String name, String comment) { + // Reuse the same Unicode-aware, value-free required-comment diagnostic. + QueryIntent.of(comment, intent.purpose()); + return new QueryRequestScope(this, name, comment, intent, null); + } + public QueryRequestScope deriveChild(String name, String comment) { + return deriveHelper(name, comment == null ? "load " + name : comment); + } + public List traceChain() { + ArrayList nodes = new ArrayList<>(); + for (QueryRequestScope current = this; current != null; current = current.parent) + nodes.add(new TraceNode(TraceKind.COMMENT, current.name, current.comment)); + nodes.add(new TraceNode(TraceKind.PURPOSE, rootRequest().name, intent.purpose())); + Collections.reverse(nodes); + return List.copyOf(nodes); + } + @Override public String toString() { return "QueryRequestScope[name=" + name + ", hasParent=" + (parent != null) + "]"; } +} diff --git a/teaql-core/src/main/java/io/teaql/core/QueryRequestSnapshot.java b/teaql-core/src/main/java/io/teaql/core/QueryRequestSnapshot.java new file mode 100644 index 00000000..e0426b7c --- /dev/null +++ b/teaql-core/src/main/java/io/teaql/core/QueryRequestSnapshot.java @@ -0,0 +1,91 @@ +package io.teaql.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** Framework query-body copy with an immutable header; never mutates a reusable builder. */ +@SuppressWarnings({"unchecked", "rawtypes"}) +public class QueryRequestSnapshot extends BaseRequest { + private final transient QueryRequestScope scope; + private final transient SearchRequest original; + private final String typeName; + private final transient SearchRequest diagnosticSource; + protected QueryRequestSnapshot(Class returnType, String typeName) { + super(returnType); + this.typeName = typeName; original = null; scope = null; diagnosticSource = null; + } + public QueryRequestSnapshot(SearchRequest request, QueryRequestScope scope) { + this(request, scope, request.diagnosticSource()); + } + public QueryRequestSnapshot(SearchRequest request, QueryRequestScope scope, SearchRequest diagnosticSource) { + super((Class) request.returnType(), request::internalNewEntity); + original = request; + typeName = request.getTypeName(); + this.scope = scope; + this.diagnosticSource = diagnosticSource; + if (request instanceof BaseRequest base) base.propagateMetadata(this); + comment = scope == null ? request.comment() : scope.intent().comment(); + purpose = scope == null ? request.purpose() : scope.purpose(); + projections = new ArrayList<>(request.getProjections()); + simpleDynamicProperties = new ArrayList<>(request.getSimpleDynamicProperties()); + dynamicPropertyMetadata = request.getDynamicPropertyMetadata(); + searchCriteria = request.getSearchCriteria(); + orderBys = new OrderBys(); + if (request.getOrderBy() != null) for (OrderBy source : request.getOrderBy().getOrderBys()) { + OrderBy order = new OrderBy("id"); order.setExpression(source.getExpression()); order.setDirection(source.getDirection()); + orderBys.addOrderBy(order); + } + if (request.getSlice() == null) slice = null; + else { slice = new Slice(); slice.setOffset(request.getSlice().getOffset()); slice.setSize(request.getSlice().getSize()); } + partitionProperty = request.getPartitionProperty(); + aggregations = copyAggregations(request.getAggregations()); + enhanceRelations = copyRequests(request.enhanceRelations()); + enhanceChildren = copyRequests(request.enhanceChildren()); + propagateAggregations = copyRequests(request.getPropagateAggregations()); + propagateDimensions = copyRequests(request.getPropagateDimensions()); + dynamicAggregateAttributes = new ArrayList<>(); + for (SimpleAggregation source : request.getDynamicAggregateAttributes()) { + var aggregate = new SimpleAggregation(source.getName(), new QueryRequestSnapshot(source.getAggregateRequest(), source.getAggregateRequest().requestScope()), source.isSingleNumber()); + aggregate.setOperation(source.getOperation()); + dynamicAggregateAttributes.add(aggregate); + } + facetRequests = new ArrayList<>(); + for (FacetRequest source : request.getFacetRequests()) { + var facet = new FacetRequest(); + facet.setFacetName(source.getFacetName()); facet.setRelationName(source.getRelationName()); + facet.setMergeCriteria(source.isMergeCriteria()); facet.setOperation(source.getOperation()); + if (source.getRequest() != null) facet.setRequest(new QueryRequestSnapshot(source.getRequest(), source.getRequest().requestScope())); + facetRequests.add(facet); + } + queryOperations = new HashMap<>(request.queryOperations()); + pageCountOperation = request.pageCountOperation(); + cacheAggregation = request.tryCacheAggregation(); aggregateCacheTime = request.getAggregateCacheTime(); + hardLimit = request.hardLimit(); continuousPageFetchOptions = request.continuousPageFetchOptions(); + idSetPaginationOptions = request.idSetPaginationOptions(); topNProbeParentThreshold = request.topNProbeParentThreshold(); + if (request.getExtensions() != null) extensions.putAll(request.getExtensions()); + searchForText = request.getSearchForText(); dynamicFieldSelection = request.getDynamicFieldSelection(); + } + private static java.util.Map copyRequests(java.util.Map requests) { + var result = new HashMap(); + if (requests != null) requests.forEach((name, query) -> result.put(name, new QueryRequestSnapshot(query, query.requestScope()))); + return result; + } + private static Aggregations copyAggregations(Aggregations source) { + Aggregations result = new Aggregations(); + if (source != null) { + result.setName(source.getName()); result.setAggregates(new ArrayList<>(source.getAggregates())); + result.setSimpleDimensions(new ArrayList<>(source.getSimpleDimensions())); + result.setComplexDimensions(new ArrayList<>(source.getComplexDimensions())); + } + return result; + } + @Override public String getTypeName() { return typeName; } + @Override public QueryIntent inheritedQueryIntent() { return scope != null ? scope.intent() : original == null ? null : original.inheritedQueryIntent(); } + @Override public QueryRequestScope requestScope() { return scope; } + @Override public SearchRequest diagnosticSource() { return diagnosticSource; } + @Override public java.util.function.Consumer statementObserver() { return original == null ? null : original.statementObserver(); } + @Override public SqlIntentRedactions sqlIntentSource() { return original == null ? null : original.sqlIntentSource(); } + @Override public List sqlTraceSource() { return original == null ? List.of() : original.sqlTraceSource(); } + @Override public boolean tryUseSubQuery() { return original == null || original.tryUseSubQuery(); } +} diff --git a/teaql-core/src/main/java/io/teaql/core/SearchRequest.java b/teaql-core/src/main/java/io/teaql/core/SearchRequest.java index 707da0a6..a421b4bd 100644 --- a/teaql-core/src/main/java/io/teaql/core/SearchRequest.java +++ b/teaql-core/src/main/java/io/teaql/core/SearchRequest.java @@ -51,6 +51,28 @@ default T internalNewEntity() { String comment(); + @com.fasterxml.jackson.annotation.JsonIgnore + @FrameworkInternal("Private execution ownership; not query JSON or Context state") + default QueryRequestScope requestScope() { return null; } + + @com.fasterxml.jackson.annotation.JsonIgnore + default Map queryOperations() { return Map.of(); } + + @com.fasterxml.jackson.annotation.JsonIgnore + default QueryOperation pageCountOperation() { return null; } + + @com.fasterxml.jackson.annotation.JsonIgnore + @FrameworkInternal("Diagnostic-only removed AST; never an execution owner or wire field") + default SearchRequest diagnosticSource() { return null; } + + @com.fasterxml.jackson.annotation.JsonIgnore + @FrameworkInternal("Invocation-owned SQL collector, never serialized or stored on Context") + default java.util.function.Consumer statementObserver() { return null; } + + @com.fasterxml.jackson.annotation.JsonIgnore + @FrameworkInternal("Invocation-local privacy provenance, not remote query input") + default SqlIntentRedactions sqlIntentSource() { return null; } + /** Explicit originating intent carried only by framework-owned derived queries. */ @FrameworkInternal("Nested query provenance; never inferred from UserContext trace state") default QueryIntent inheritedQueryIntent() { return null; } diff --git a/teaql-core/src/main/java/io/teaql/core/SimpleAggregation.java b/teaql-core/src/main/java/io/teaql/core/SimpleAggregation.java index bcfe06d0..17354224 100644 --- a/teaql-core/src/main/java/io/teaql/core/SimpleAggregation.java +++ b/teaql-core/src/main/java/io/teaql/core/SimpleAggregation.java @@ -4,6 +4,11 @@ public class SimpleAggregation implements Expression { private String name; + private QueryOperation operation; + @com.fasterxml.jackson.annotation.JsonIgnore + public QueryOperation getOperation() { return operation; } + @com.fasterxml.jackson.annotation.JsonIgnore + public void setOperation(QueryOperation operation) { this.operation = operation; } private SearchRequest aggregateRequest; private boolean singleNumber; diff --git a/teaql-core/src/main/java/io/teaql/core/SmartList.java b/teaql-core/src/main/java/io/teaql/core/SmartList.java index ae7c6073..2a23a4ce 100644 --- a/teaql-core/src/main/java/io/teaql/core/SmartList.java +++ b/teaql-core/src/main/java/io/teaql/core/SmartList.java @@ -22,6 +22,9 @@ public class SmartList implements Iterable { List data; List aggregationResults; + private String pageCountAlias = TeaQLConstants.ROOT_LIST_PARAMETER_NAME; + @FrameworkInternal("Named page total metadata, independent of physical count SQL") + public void __internalSetPageCountAlias(String alias) { ensureMutable(); pageCountAlias = alias; } Map facets; @@ -200,7 +203,7 @@ public int getTotalCount() { if (numberProps.isEmpty()) { return size(); } - Object count = numberProps.get(TeaQLConstants.ROOT_LIST_PARAMETER_NAME); + Object count = numberProps.get(pageCountAlias); if (count instanceof Number intCount) { return intCount.intValue(); } diff --git a/teaql-core/src/main/java/io/teaql/core/SqlExecutionCorrelation.java b/teaql-core/src/main/java/io/teaql/core/SqlExecutionCorrelation.java new file mode 100644 index 00000000..797b57b7 --- /dev/null +++ b/teaql-core/src/main/java/io/teaql/core/SqlExecutionCorrelation.java @@ -0,0 +1,4 @@ +package io.teaql.core; + +/** Statement-owned telemetry identity; never derived from log order or an active span at completion. */ +public record SqlExecutionCorrelation(String traceId, String spanId) {} diff --git a/teaql-core/src/main/java/io/teaql/core/SqlExecutionTrace.java b/teaql-core/src/main/java/io/teaql/core/SqlExecutionTrace.java index 65304eb5..5d789352 100644 --- a/teaql-core/src/main/java/io/teaql/core/SqlExecutionTrace.java +++ b/teaql-core/src/main/java/io/teaql/core/SqlExecutionTrace.java @@ -6,7 +6,12 @@ /** Immutable statement-owned source path and separate graph mutation lineage. */ public record SqlExecutionTrace(List source, List mutationLineage, String operation, @com.fasterxml.jackson.annotation.JsonIgnore java.util.function.Consumer statementObserver, - @com.fasterxml.jackson.annotation.JsonIgnore MutationIntent mutationIntent) { + @com.fasterxml.jackson.annotation.JsonIgnore MutationIntent mutationIntent, + @com.fasterxml.jackson.annotation.JsonIgnore QueryRequestScope queryScope) { + public SqlExecutionTrace(List source, List mutationLineage, String operation, + java.util.function.Consumer statementObserver, MutationIntent mutationIntent) { + this(source, mutationLineage, operation, statementObserver, mutationIntent, null); + } public SqlExecutionTrace(List source, List mutationLineage, String operation) { this(source, mutationLineage, operation, null, null); } @@ -17,7 +22,7 @@ public SqlExecutionTrace(List source, List mutationLineage /** Invocation-owned collection; never installed on Context or a cached repository. */ public SqlExecutionTrace collecting(java.util.function.Consumer observer) { - return new SqlExecutionTrace(source, mutationLineage, operation, observer, mutationIntent); + return new SqlExecutionTrace(source, mutationLineage, operation, observer, mutationIntent, queryScope); } public void recordStatement(ExecutionMetadata metadata) { @@ -25,6 +30,11 @@ public void recordStatement(ExecutionMetadata metadata) { metadata.setTraceChain(path.path()); metadata.setComment(path.comment()); metadata.setPurpose(path.purpose()); + if (queryScope != null) { + metadata.setRequestTraceChain(queryScope.traceChain()); + metadata.setComment(queryScope.intent().comment()); + metadata.setPurpose(queryScope.purpose()); + } // Canonicalization extracts the last local reason for standalone source // vectors. Executed mutations instead own their root intent independently // of the graph lineage, including write readback and prepared batches. @@ -52,7 +62,7 @@ public static SqlExecutionTrace query(SearchRequest request) { source = List.of(new TraceNode(TraceKind.COMMENT, request.getTypeName(), intent.comment()), new TraceNode(TraceKind.PURPOSE, request.getTypeName(), intent.purpose())); } - return new SqlExecutionTrace(source, List.of(), "select"); + return new SqlExecutionTrace(source, List.of(), "select", null, null, QueryRequestScope.capture(request)); } public SqlExecutionTrace readback(MutationIntent intent) { @@ -65,6 +75,7 @@ public SqlExecutionTrace readback(MutationIntent intent) { public void applyTo(ExecutionMetadata metadata) { metadata.setTraceChain(source); + if (queryScope != null) metadata.setRequestTraceChain(queryScope.traceChain()); metadata.setMutationLineage(mutationLineage); metadata.setStatementOperation(operation); } diff --git a/teaql-core/src/main/java/io/teaql/core/SqlStatementObservation.java b/teaql-core/src/main/java/io/teaql/core/SqlStatementObservation.java new file mode 100644 index 00000000..d14f30aa --- /dev/null +++ b/teaql-core/src/main/java/io/teaql/core/SqlStatementObservation.java @@ -0,0 +1,36 @@ +package io.teaql.core; + +/** SDK-neutral physical execution lifecycle. Implementations must be fail-open. */ +public interface SqlStatementObservation { + default boolean enabled() { return false; } + default SqlExecutionCorrelation correlation() { return null; } + default void success() {} + default void failure(Throwable failure) {} + /** Release initial thread activation before returning a lazy cursor. */ + default void detach() {} + default Activation activate() { return () -> {}; } + interface Activation extends AutoCloseable { @Override void close(); } + SqlStatementObservation NOOP = new SqlStatementObservation() {}; + + static SqlStatementObservation startSafely(UserContext context, String backend, DataServiceOperation operation) { + try { + var observation = context.startSqlStatement(backend, operation); + if (observation == null || observation == NOOP) return NOOP; + return new SqlStatementObservation() { + @Override public boolean enabled() { try { return observation.enabled(); } catch (Throwable ignored) { return false; } } + @Override public SqlExecutionCorrelation correlation() { try { return observation.correlation(); } catch (Throwable ignored) { return null; } } + @Override public void success() { try { observation.success(); } catch (Throwable ignored) {} } + @Override public void failure(Throwable error) { try { observation.failure(error); } catch (Throwable ignored) {} } + @Override public void detach() { try { observation.detach(); } catch (Throwable ignored) {} } + @Override public Activation activate() { + try { + var scope = observation.activate(); + return () -> { try { if (scope != null) scope.close(); } catch (Throwable ignored) {} }; + } catch (Throwable ignored) { return () -> {}; } + } + }; + } catch (Throwable ignored) { + return NOOP; + } + } +} diff --git a/teaql-core/src/main/java/io/teaql/core/UserContext.java b/teaql-core/src/main/java/io/teaql/core/UserContext.java index 5889ef04..19520f2c 100644 --- a/teaql-core/src/main/java/io/teaql/core/UserContext.java +++ b/teaql-core/src/main/java/io/teaql/core/UserContext.java @@ -203,16 +203,16 @@ default boolean requiresSensitiveSqlLogData() { void recordExecutionMetadata(ExecutionMetadata metadata); + @FrameworkInternal("SQL provider observation only") + default SqlStatementObservation startSqlStatement(String backend, DataServiceOperation operation) { + return SqlStatementObservation.NOOP; + } + // Business-facing API T executeForOne(ExecutableRequest request); SmartList executeForList(ExecutableRequest request); - default SmartList executeForPage( - ExecutableRequest request, int offset, int limit) { - throw new TeaQLRuntimeException("Exact paged execution is not supported by this context"); - } - Stream executeForStream(ExecutableRequest request); Stream executeForStream(ExecutableRequest request, int enhanceBatchSize); diff --git a/teaql-core/src/test/java/io/teaql/core/SqlExecutionTraceTest.java b/teaql-core/src/test/java/io/teaql/core/SqlExecutionTraceTest.java index a54f9694..beaed7e9 100644 --- a/teaql-core/src/test/java/io/teaql/core/SqlExecutionTraceTest.java +++ b/teaql-core/src/test/java/io/teaql/core/SqlExecutionTraceTest.java @@ -29,6 +29,9 @@ static final class Request extends BaseRequest { @Test public void derivedStatementFreezesCompleteOrderedSource() { var request = new Request(); + // The legacy physical vector no longer supplies missing request intent. + request.comment = "load payment context"; + request.purpose = "render details"; request.source = new ArrayList<>(List.of( new TraceNode(TraceKind.COMMENT, "PaymentAttempt", "load payment context"), new TraceNode(TraceKind.PURPOSE, "PaymentAttempt", "render details"), @@ -44,6 +47,13 @@ static final class Request extends BaseRequest { assertEquals("customerOrder", canonical.path().get(3).getName()); } + @Test public void physicalFramesCannotAuthorizeAnUnownedRoot() { + var request = new Request(); + request.source = List.of(new TraceNode(TraceKind.COMMENT, "CustomerOrder", "forged comment"), + new TraceNode(TraceKind.PURPOSE, "CustomerOrder", "forged purpose")); + assertThrows(RequestIntentException.class, () -> SqlExecutionTrace.query(request)); + } + @Test public void mutationStatementOwnsRootIntentWithAndWithoutAnObserver() { var entity = new BaseEntity(); entity.__internalSet("id", 201L); diff --git a/teaql-data-service-sql/pom.xml b/teaql-data-service-sql/pom.xml index 88126ffe..fc6a44ea 100644 --- a/teaql-data-service-sql/pom.xml +++ b/teaql-data-service-sql/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-data-service-sql/src/main/java/io/teaql/dataservice/sql/SqlDataServiceExecutor.java b/teaql-data-service-sql/src/main/java/io/teaql/dataservice/sql/SqlDataServiceExecutor.java index 8ab2d4d5..d120e2ec 100644 --- a/teaql-data-service-sql/src/main/java/io/teaql/dataservice/sql/SqlDataServiceExecutor.java +++ b/teaql-data-service-sql/src/main/java/io/teaql/dataservice/sql/SqlDataServiceExecutor.java @@ -200,26 +200,8 @@ public java.util.List> query(io.teaql.core.UserCon @Override public java.util.List> query(io.teaql.core.UserContext context, String sql, Object[] args, io.teaql.core.sql.portable.SqlLogBindings bindings) { - boolean logging = context.isQueryExecutionLoggingEnabled(); - boolean collecting = logging || bindings.collectsStatements(); - long start = collecting ? System.nanoTime() : 0L; - java.util.List> res = diagnosed(context, sql, args, bindings, - io.teaql.core.DataServiceOperation.QUERY, logging, start, - () -> executionAdapter.queryForList(sql, args)); - if (!collecting) return res; - long elapsed = (System.nanoTime() - start) / 1000; - io.teaql.core.ExecutionMetadata meta = new io.teaql.core.ExecutionMetadata(); - meta.setBackend(debugDatabaseKind.toLowerCase(java.util.Locale.ROOT)); - meta.setOperation(io.teaql.core.DataServiceOperation.QUERY); - meta.setElapsedUs(elapsed); - meta.setResultCount(res.size()); - meta.setResultSummary("Fetched " + res.size() + " rows"); - meta.setExecutionOutcome("success"); - meta.setParameterizedQuery(sql); - meta.setParameters(parameters(args)); - bindings.applyTo(meta); - recordStatement(context, bindings, meta, logging); - return res; + return observedStatement(context, sql, args, bindings, io.teaql.core.DataServiceOperation.QUERY, + () -> executionAdapter.queryForList(sql, args), (meta, result) -> meta.setResultCount(result.size())); } @Override @@ -233,26 +215,8 @@ public java.util.List query( public java.util.List query( io.teaql.core.UserContext context, String sql, Object[] args, io.teaql.core.CompiledRowMapper rowMapper, io.teaql.core.sql.portable.SqlLogBindings bindings) { - boolean logging = context.isQueryExecutionLoggingEnabled(); - boolean collecting = logging || bindings.collectsStatements(); - long start = collecting ? System.nanoTime() : 0L; - java.util.List res = diagnosed(context, sql, args, bindings, - io.teaql.core.DataServiceOperation.QUERY, logging, start, - () -> executionAdapter.query(sql, args, rowMapper)); - if (!collecting) return res; - long elapsed = (System.nanoTime() - start) / 1000; - io.teaql.core.ExecutionMetadata meta = new io.teaql.core.ExecutionMetadata(); - meta.setBackend(debugDatabaseKind.toLowerCase(java.util.Locale.ROOT)); - meta.setOperation(io.teaql.core.DataServiceOperation.QUERY); - meta.setElapsedUs(elapsed); - meta.setResultCount(res.size()); - meta.setResultSummary("Fetched " + res.size() + " typed rows"); - meta.setExecutionOutcome("success"); - meta.setParameterizedQuery(sql); - meta.setParameters(parameters(args)); - bindings.applyTo(meta); - recordStatement(context, bindings, meta, logging); - return res; + return observedStatement(context, sql, args, bindings, io.teaql.core.DataServiceOperation.QUERY, + () -> executionAdapter.query(sql, args, rowMapper), (meta, result) -> meta.setResultCount(result.size())); } @Override @@ -263,26 +227,8 @@ public int executeUpdate(io.teaql.core.UserContext context, String sql, Object[] @Override public int executeUpdate(io.teaql.core.UserContext context, String sql, Object[] args, io.teaql.core.sql.portable.SqlLogBindings bindings) { - boolean logging = context.isMutationExecutionLoggingEnabled(); - boolean collecting = logging || bindings.collectsStatements(); - long start = collecting ? System.nanoTime() : 0L; - int res = diagnosed(context, sql, args, bindings, - io.teaql.core.DataServiceOperation.MUTATION, logging, start, - () -> executionAdapter.update(sql, args)); - if (!collecting) return res; - long elapsed = (System.nanoTime() - start) / 1000; - io.teaql.core.ExecutionMetadata meta = new io.teaql.core.ExecutionMetadata(); - meta.setBackend(debugDatabaseKind.toLowerCase(java.util.Locale.ROOT)); - meta.setOperation(io.teaql.core.DataServiceOperation.MUTATION); - meta.setElapsedUs(elapsed); - meta.setAffectedRows((long) res); - meta.setResultSummary("Affected " + res + " rows"); - meta.setExecutionOutcome("success"); - meta.setParameterizedQuery(sql); - meta.setParameters(parameters(args)); - bindings.applyTo(meta); - recordStatement(context, bindings, meta, logging); - return res; + return observedStatement(context, sql, args, bindings, io.teaql.core.DataServiceOperation.MUTATION, + () -> executionAdapter.update(sql, args), (meta, result) -> meta.setAffectedRows(result.longValue())); } @Override @@ -294,41 +240,37 @@ public int[] batchUpdate(io.teaql.core.UserContext context, String sql, java.uti public int[] batchUpdate(io.teaql.core.UserContext context, String sql, java.util.List batchArgs, io.teaql.core.sql.portable.SqlLogBindings bindings) { bindings.validateBatchSize(batchArgs == null ? 0 : batchArgs.size()); + var observation = io.teaql.core.SqlStatementObservation.startSafely(context, debugDatabaseKind, + io.teaql.core.DataServiceOperation.MUTATION); boolean logging = context.isMutationExecutionLoggingEnabled(); - boolean collecting = logging || bindings.collectsStatements(); + boolean collecting = logging || bindings.collectsStatements() || observation.enabled(); long start = collecting ? System.nanoTime() : 0L; int[] res; try { res = executionAdapter.batchUpdate(sql, batchArgs); } catch (RuntimeException failure) { - if (collecting) recordBatch(context, sql, batchArgs, bindings, start, batchCounts(failure), failure, logging); + try { + if (collecting) recordBatch(context, sql, batchArgs, bindings, start, batchCounts(failure), failure, + logging, observation.correlation()); + } finally { observation.failure(failure); } + throw failure; + } catch (Error failure) { + observation.failure(failure); throw failure; } - if (collecting) recordBatch(context, sql, batchArgs, bindings, start, - res == null ? null : java.util.Arrays.stream(res).asLongStream().toArray(), null, logging); + try { + if (collecting) recordBatch(context, sql, batchArgs, bindings, start, + res == null ? null : java.util.Arrays.stream(res).asLongStream().toArray(), null, logging, + observation.correlation()); + } finally { observation.success(); } return res; } @Override public void execute(io.teaql.core.UserContext context, String sql) { - boolean logging = context.isMutationExecutionLoggingEnabled(); - long start = logging ? System.nanoTime() : 0L; - diagnosed(context, sql, null, io.teaql.core.sql.portable.SqlLogBindings.UNKNOWN, - io.teaql.core.DataServiceOperation.SCHEMA, logging, start, - () -> { executionAdapter.execute(sql); return null; }); - if (!logging) return; - long elapsed = (System.nanoTime() - start) / 1000; - io.teaql.core.ExecutionMetadata meta = new io.teaql.core.ExecutionMetadata(); - meta.setBackend(debugDatabaseKind.toLowerCase(java.util.Locale.ROOT)); - meta.setOperation(io.teaql.core.DataServiceOperation.SCHEMA); - meta.setElapsedUs(elapsed); - meta.setResultSummary("Executed"); - meta.setExecutionOutcome("success"); - meta.setParameterizedQuery(sql); - if (context.requiresSensitiveSqlLogData()) { - meta.setDebugQuery(sql); - } - context.recordExecutionMetadata(meta); + observedStatement(context, sql, null, io.teaql.core.sql.portable.SqlLogBindings.UNKNOWN, + io.teaql.core.DataServiceOperation.SCHEMA, + () -> { executionAdapter.execute(sql); return null; }, (meta, result) -> meta.setResultSummary("Executed")); } }; io.teaql.core.sql.portable.PortableSQLDataService portableService = @@ -341,11 +283,13 @@ public void execute(io.teaql.core.UserContext context, String sql) { private java.util.stream.Stream observedQueryStream(UserContext context, String sql, Object[] args, io.teaql.core.sql.portable.SqlLogBindings bindings, java.util.function.Supplier> action) { boolean logging = context.isQueryExecutionLoggingEnabled(); - boolean collecting = logging || bindings.collectsStatements(); + var observation = io.teaql.core.SqlStatementObservation.startSafely(context, debugDatabaseKind, io.teaql.core.DataServiceOperation.QUERY); + boolean collecting = logging || bindings.collectsStatements() || observation.enabled(); long start = collecting ? System.nanoTime() : 0L; io.teaql.core.ExecutionMetadata meta = null; if (collecting) { meta = statementMetadata(context, sql, args, bindings, io.teaql.core.DataServiceOperation.QUERY); + meta.setSqlExecutionCorrelation(observation.correlation()); // Snapshot before request trace scopes are popped; consumption is lazy. if (bindings.executionTrace() == null) { var trace = context.getTraceChain(); @@ -354,9 +298,22 @@ private java.util.stream.Stream observedQueryStream(UserContext context, : java.util.List.copyOf(trace)); } } - var stream = diagnosed(context, sql, args, bindings, io.teaql.core.DataServiceOperation.QUERY, - logging, start, action); - return SqlDiagnosticStream.wrap(context, stream, meta, start, bindings, logging); + java.util.stream.Stream stream; + try { stream = action.get(); } + catch (RuntimeException | Error failure) { + try { + if (meta != null) { + meta.setElapsedUs((System.nanoTime() - start) / 1000); + meta.setExecutionOutcome(failure instanceof java.util.concurrent.CancellationException ? "cancelled" : "failure"); + meta.setResultSummary("Statement did not complete; row count unknown"); + recordStatement(context, bindings, meta, logging); + } + } catch (RuntimeException | Error diagnosticFailure) { /* Keep the original driver error. */ } + finally { observation.failure(failure); } + throw failure; + } + observation.detach(); + return SqlDiagnosticStream.wrap(context, stream, meta, start, bindings, logging, observation); } private io.teaql.core.ExecutionMetadata statementMetadata(UserContext context, String sql, Object[] args, @@ -381,12 +338,13 @@ private static long[] batchCounts(Throwable failure) { private void recordBatch(UserContext context, String sql, java.util.List batchArgs, io.teaql.core.sql.portable.SqlLogBindings bindings, long start, long[] counts, RuntimeException failure, - boolean logging) { + boolean logging, io.teaql.core.SqlExecutionCorrelation correlation) { long elapsed = (System.nanoTime() - start) / 1000; int size = batchArgs == null ? 0 : batchArgs.size(); for (int row = 0; row < Math.max(size, failure == null ? 0 : 1); row++) { var meta = statementMetadata(context, sql, row < size ? batchArgs.get(row) : null, row < size ? bindings.forBatchRow(row) : bindings, io.teaql.core.DataServiceOperation.MUTATION); + meta.setSqlExecutionCorrelation(correlation); meta.setElapsedUs(row == 0 ? elapsed : 0); meta.setBatchOutcome(failure == null ? "success" : failure instanceof java.util.concurrent.CancellationException ? "cancelled" : "failure"); @@ -405,41 +363,56 @@ private void recordBatch(UserContext context, String sql, java.util.List T diagnosed(UserContext context, String sql, Object[] args, + private T observedStatement(UserContext context, String sql, Object[] args, io.teaql.core.sql.portable.SqlLogBindings bindings, - io.teaql.core.DataServiceOperation operation, boolean logging, long start, - java.util.function.Supplier execute) { + io.teaql.core.DataServiceOperation operation, java.util.function.Supplier execute, + java.util.function.BiConsumer resultMetadata) { + var observation = io.teaql.core.SqlStatementObservation.startSafely(context, debugDatabaseKind, operation); + boolean logging = operation == io.teaql.core.DataServiceOperation.QUERY + ? context.isQueryExecutionLoggingEnabled() : context.isMutationExecutionLoggingEnabled(); + boolean collecting = logging || bindings.collectsStatements() || observation.enabled(); + long start = collecting ? System.nanoTime() : 0L; + T result; try { - return execute.get(); - } catch (RuntimeException failure) { - if (logging || bindings.collectsStatements()) { - io.teaql.core.ExecutionMetadata meta = new io.teaql.core.ExecutionMetadata(); - meta.setBackend(debugDatabaseKind.toLowerCase(java.util.Locale.ROOT)); - meta.setOperation(operation); + result = execute.get(); + } catch (RuntimeException | Error failure) { + try { + if (collecting) { + var meta = statementMetadata(context, sql, args, bindings, operation); + meta.setSqlExecutionCorrelation(observation.correlation()); meta.setElapsedUs((System.nanoTime() - start) / 1000); meta.setExecutionOutcome(failure instanceof java.util.concurrent.CancellationException ? "cancelled" : "failure"); // Driver errors may contain raw bind values. Keep the error for the caller, // never copy its message/cause into diagnostics or invent affected-row counts. meta.setResultSummary("Statement did not complete; row count unknown"); - meta.setParameterizedQuery(sql); - meta.setParameters(parameters(args)); - bindings.applyTo(meta); - try { - recordStatement(context, bindings, meta, logging); - } catch (RuntimeException diagnosticFailure) { - // Preserve the original driver error; no unsafe fallback logger. - } - } + recordStatement(context, bindings, meta, logging); + } + } catch (RuntimeException | Error diagnosticFailure) { /* Preserve the original driver error. */ } + finally { observation.failure(failure); } throw failure; } + try { + if (collecting) { + var meta = statementMetadata(context, sql, args, bindings, operation); + meta.setSqlExecutionCorrelation(observation.correlation()); + meta.setElapsedUs((System.nanoTime() - start) / 1000); + meta.setExecutionOutcome("success"); + resultMetadata.accept(meta, result); + meta.setResultSummary(meta.getResultSummary() != null ? meta.getResultSummary() + : meta.getResultCount() != null ? "Fetched " + meta.getResultCount() + " rows" + : "Affected " + meta.getAffectedRows() + " rows"); + recordStatement(context, bindings, meta, logging); + } + } finally { observation.success(); } + return result; } private static void recordStatement(UserContext context, io.teaql.core.sql.portable.SqlLogBindings bindings, io.teaql.core.ExecutionMetadata metadata, boolean logging) { bindings.recordStatement(metadata); - if (logging) context.recordExecutionMetadata(metadata); + if (logging || metadata.getSqlExecutionCorrelation() != null) context.recordExecutionMetadata(metadata); } public static String debugSql(String sql, Object[] args) { diff --git a/teaql-data-service-sql/src/main/java/io/teaql/dataservice/sql/SqlDiagnosticStream.java b/teaql-data-service-sql/src/main/java/io/teaql/dataservice/sql/SqlDiagnosticStream.java index 308db5c0..4d2bad22 100644 --- a/teaql-data-service-sql/src/main/java/io/teaql/dataservice/sql/SqlDiagnosticStream.java +++ b/teaql-data-service-sql/src/main/java/io/teaql/dataservice/sql/SqlDiagnosticStream.java @@ -16,24 +16,28 @@ final class SqlDiagnosticStream implements Spliterator { private final long start; private final io.teaql.core.sql.portable.SqlLogBindings bindings; private final boolean logging; + private final io.teaql.core.SqlStatementObservation observation; private Spliterator rows; private boolean done; private long delivered; private SqlDiagnosticStream(UserContext context, Stream source, ExecutionMetadata metadata, long start, - io.teaql.core.sql.portable.SqlLogBindings bindings, boolean logging) { + io.teaql.core.sql.portable.SqlLogBindings bindings, boolean logging, + io.teaql.core.SqlStatementObservation observation) { this.context = context; this.source = source; this.metadata = metadata; this.start = start; this.bindings = bindings; this.logging = logging; + this.observation = observation; } static Stream wrap(UserContext context, Stream source, ExecutionMetadata metadata, long start, - io.teaql.core.sql.portable.SqlLogBindings bindings, boolean logging) { - var cursor = new SqlDiagnosticStream<>(context, source, metadata, start, bindings, logging); - try { + io.teaql.core.sql.portable.SqlLogBindings bindings, boolean logging, + io.teaql.core.SqlStatementObservation observation) { + var cursor = new SqlDiagnosticStream<>(context, source, metadata, start, bindings, logging, observation); + try (var activation = observation.activate()) { cursor.rows = source.spliterator(); } catch (RuntimeException | Error failure) { cursor.finish(outcome(failure), failure); @@ -44,7 +48,7 @@ static Stream wrap(UserContext context, Stream source, ExecutionMetada @Override public boolean tryAdvance(Consumer action) { if (done) return false; - try { + try (var activation = observation.activate()) { boolean more = rows.tryAdvance(row -> { // Count rows handed to the caller, not prefetched rows or rows successfully processed. delivered++; @@ -68,6 +72,7 @@ private void finish(String outcome, Throwable original) { if (failure == null) { failure = closeFailure; outcome = outcome(closeFailure); } else if (failure != closeFailure) failure.addSuppressed(closeFailure); } + Throwable executionFailure = failure; if (metadata != null) { metadata.setElapsedUs((System.nanoTime() - start) / 1000); metadata.setExecutionOutcome(outcome); @@ -75,11 +80,14 @@ private void finish(String outcome, Throwable original) { metadata.setResultSummary("Cursor " + outcome + "; delivered " + delivered + " rows"); try { bindings.recordStatement(metadata); - if (logging) context.recordExecutionMetadata(metadata); + if (logging || metadata.getSqlExecutionCorrelation() != null) context.recordExecutionMetadata(metadata); } catch (RuntimeException | Error sinkFailure) { if (failure == null) failure = sinkFailure; } } + if (executionFailure != null) observation.failure(executionFailure); + else if ("cancelled".equals(outcome)) observation.failure(new CancellationException()); + else observation.success(); // The original traversal exception is rethrown by the caller of finish. if (original == null) { if (failure instanceof RuntimeException runtimeFailure) throw runtimeFailure; diff --git a/teaql-data-service-sql/src/test/java/io/teaql/dataservice/sql/SqlDataServiceExecutorTest.java b/teaql-data-service-sql/src/test/java/io/teaql/dataservice/sql/SqlDataServiceExecutorTest.java index 59380def..2c2f1329 100644 --- a/teaql-data-service-sql/src/test/java/io/teaql/dataservice/sql/SqlDataServiceExecutorTest.java +++ b/teaql-data-service-sql/src/test/java/io/teaql/dataservice/sql/SqlDataServiceExecutorTest.java @@ -51,6 +51,8 @@ public void testQueryPlaceholder() { SqlDataServiceExecutor executor = new SqlDataServiceExecutor("sql", new MockSqlExecutionAdapter()); Assert.assertThrows(io.teaql.core.TeaQLRuntimeException.class, () -> { executor.query(null, new QueryRequest() { + private final io.teaql.core.QueryRequestScope scope = io.teaql.core.QueryRequestScope.root("unsupported", intent()); + @Override public io.teaql.core.QueryRequestScope requestScope() { return scope; } @Override public io.teaql.core.QueryIntent intent() { return io.teaql.core.QueryIntent.of("test unsupported request", "verify provider rejects unknown envelope"); } diff --git a/teaql-data-service-sql/src/test/java/io/teaql/dataservice/sql/SqlStatementTelemetryTest.java b/teaql-data-service-sql/src/test/java/io/teaql/dataservice/sql/SqlStatementTelemetryTest.java new file mode 100644 index 00000000..ff5cd35f --- /dev/null +++ b/teaql-data-service-sql/src/test/java/io/teaql/dataservice/sql/SqlStatementTelemetryTest.java @@ -0,0 +1,160 @@ +package io.teaql.dataservice.sql; + +import io.teaql.core.*; +import io.teaql.core.meta.SimpleEntityMetaFactory; +import io.teaql.core.sql.portable.*; +import io.teaql.runtime.*; +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.stream.Stream; +import org.junit.Test; +import static org.junit.Assert.*; + +public class SqlStatementTelemetryTest { + private static final String SQL = "SELECT id FROM school WHERE name = ?"; + private static final SqlLogBindings BINDINGS = new SqlLogBindings(List.of(SqlParameterLogPolicy.MASKED), true); + private final Recording telemetry = new Recording(); + private final AtomicInteger ordinaryLogs = new AtomicInteger(); + private final DefaultUserContext context = new DefaultUserContext(TeaQLRuntime.builder() + .metadata(new SimpleEntityMetaFactory()).telemetry(telemetry) + .queryExecutionLogging(false).mutationExecutionLogging(false) + .logSink((caller, metadata) -> ordinaryLogs.incrementAndGet()).build()); + + private TeaQLDatabase database(InvocationHandler action) throws Exception { + var adapter = (SqlExecutionAdapter) Proxy.newProxyInstance(SqlExecutionAdapter.class.getClassLoader(), + new Class[]{SqlExecutionAdapter.class}, action); + var factory = SqlDataServiceExecutor.class.getDeclaredMethod("createPortableService", io.teaql.core.meta.EntityMetaFactory.class); + factory.setAccessible(true); + var service = factory.invoke(new SqlDataServiceExecutor("sql", adapter), new SimpleEntityMetaFactory()); + var field = PortableSQLDataService.class.getDeclaredField("database"); + field.setAccessible(true); + return (TeaQLDatabase) field.get(service); + } + + @Test public void outOfOrderExecutionsKeepStatementOwnedIdentityWithLoggingDisabled() throws Exception { + var firstInDriver = new CountDownLatch(1); + var secondCompleted = new CountDownLatch(1); + var identitiesAtDriver = new ConcurrentHashMap(); + var db = database((proxy, method, args) -> { + String value = (String) ((Object[]) args[1])[0]; + identitiesAtDriver.put(value, telemetry.active.get()); + assertNotNull("Statement span must already exist at driver entry", telemetry.active.get()); + if (value.equals("FIRST-CANARY")) { + firstInDriver.countDown(); + assertTrue(secondCompleted.await(5, TimeUnit.SECONDS)); + } + return List.of(Map.of("id", 1L)); + }); + var workers = Executors.newFixedThreadPool(2); + try { + var first = workers.submit(() -> db.query(context, SQL, new Object[]{"FIRST-CANARY"}, BINDINGS)); + assertTrue(firstInDriver.await(5, TimeUnit.SECONDS)); + var second = workers.submit(() -> { + var rows = db.query(context, SQL, new Object[]{"SECOND-CANARY"}, BINDINGS); + secondCompleted.countDown(); + return rows; + }); + assertEquals(1, first.get(8, TimeUnit.SECONDS).size()); + assertEquals(1, second.get(8, TimeUnit.SECONDS).size()); + } finally { workers.shutdownNow(); } + assertEquals(2, telemetry.events.size()); + assertEquals(identitiesAtDriver.get("SECOND-CANARY"), telemetry.events.get(0).correlation().spanId()); + assertEquals(identitiesAtDriver.get("FIRST-CANARY"), telemetry.events.get(1).correlation().spanId()); + assertNotEquals(telemetry.events.get(0).correlation(), telemetry.events.get(1).correlation()); + assertEquals(0, ordinaryLogs.get()); + assertFalse(telemetry.events.toString().contains("FIRST-CANARY")); + assertFalse(telemetry.events.toString().contains("SECOND-CANARY")); + assertEquals(2, telemetry.completed.size()); + } + + @Test public void writeAndReadbackUseDistinctExecutionsAndRetainLineageWithoutEntityIds() throws Exception { + var db = database((proxy, method, args) -> method.getName().equals("update") ? 1 : List.of(Map.of("id", 17L))); + var entity = new BaseEntity(); + entity.updateId(17L); + var intent = MutationIntent.of("save school"); + var lineage = List.of(new TraceNode(TraceKind.AUDIT_REASON, "School", 99887766L, "save school")); + var trace = SqlExecutionTrace.mutation(entity, lineage, "update", intent); + db.executeUpdate(context, "UPDATE school SET name = ?", new Object[]{"PRIVATE-CANARY"}, BINDINGS.withTrace(trace)); + db.query(context, SQL, new Object[]{"PRIVATE-CANARY"}, BINDINGS.withTrace(trace.readback(intent))); + assertEquals(2, telemetry.events.size()); + assertNotEquals(telemetry.events.get(0).correlation().spanId(), telemetry.events.get(1).correlation().spanId()); + assertEquals("save school", telemetry.events.get(0).attributes().get("teaql.intent.audit_reason")); + assertEquals("save school", telemetry.events.get(1).attributes().get("teaql.intent.audit_reason")); + assertFalse(telemetry.events.toString().contains("99887766")); + assertFalse(telemetry.events.toString().contains("PRIVATE-CANARY")); + assertTrue(telemetry.events.get(0).attributes().get("teaql.mutation_lineage").toString().contains("save school")); + assertEquals(0, ordinaryLogs.get()); + } + + @Test public void lazyCursorDetachesAndKeepsItsOwnCorrelationUnderUnrelatedOperation() throws Exception { + AtomicInteger closed = new AtomicInteger(); + var db = database((proxy, method, args) -> Stream.of(Map.of("id", 1L), Map.of("id", 2L)) + .peek(row -> assertNotNull(telemetry.active.get())).onClose(closed::incrementAndGet)); + var cursor = db.queryForStream(context, SQL, new Object[]{"CURSOR-CANARY"}, BINDINGS); + assertNull("Opening a cursor must not leave its span active", telemetry.active.get()); + var unrelated = telemetry.start(new RuntimeTelemetry.Operation("query", "unrelated", Map.of())); + String unrelatedId = telemetry.active.get(); + try (cursor) { assertEquals(1, cursor.limit(1).count()); } + assertEquals(unrelatedId, telemetry.active.get()); + assertEquals(1, closed.get()); + assertEquals(1, telemetry.events.size()); + assertNotEquals(unrelatedId, telemetry.events.get(0).correlation().spanId()); + assertEquals("cancelled", telemetry.events.get(0).attributes().get("teaql.sql.outcome")); + assertEquals(1, telemetry.completed.size()); + unrelated.success(); + assertNull(telemetry.active.get()); + } + + @Test public void driverFailureIsNotReplacedAndUnknownCountsAreNotInvented() throws Exception { + var failure = new IllegalStateException("DRIVER-PRIVATE-CANARY"); + var db = database((proxy, method, args) -> { throw failure; }); + assertSame(failure, assertThrows(IllegalStateException.class, + () -> db.query(context, SQL, new Object[]{"DRIVER-PRIVATE-CANARY"}, BINDINGS))); + assertEquals(1, telemetry.events.size()); + assertEquals("failure", telemetry.events.get(0).attributes().get("teaql.sql.outcome")); + assertFalse(telemetry.events.get(0).attributes().containsKey("teaql.sql.result_count")); + assertFalse(telemetry.events.toString().contains("DRIVER-PRIVATE-CANARY")); + assertEquals(List.of("failure"), telemetry.completed); + assertNull(telemetry.active.get()); + } + + @Test public void onePhysicalBatchHasOneIdentityAndSeparateMemberOutcomes() throws Exception { + var db = database((proxy, method, args) -> new int[]{1, java.sql.Statement.SUCCESS_NO_INFO}); + assertArrayEquals(new int[]{1, -2}, db.batchUpdate(context, "UPDATE school SET name = ?", + List.of(new Object[]{"BATCH-A-CANARY"}, new Object[]{"BATCH-B-CANARY"}), BINDINGS)); + assertEquals(2, telemetry.events.size()); + assertEquals(telemetry.events.get(0).correlation(), telemetry.events.get(1).correlation()); + assertEquals("success", telemetry.events.get(1).attributes().get("teaql.sql.outcome")); + assertFalse(telemetry.events.get(1).attributes().containsKey("teaql.sql.affected_rows")); + assertEquals(1, telemetry.completed.size()); + assertEquals(0, ordinaryLogs.get()); + } + + private static final class Recording implements RuntimeTelemetry { + private final AtomicInteger next = new AtomicInteger(); + private final ThreadLocal active = new ThreadLocal<>(); + private final List events = new CopyOnWriteArrayList<>(); + private final List completed = new CopyOnWriteArrayList<>(); + @Override public boolean recordsSqlDiagnostics() { return true; } + @Override public void recordDiagnostic(Diagnostic event) { events.add(event); } + @Override public Scope start(Operation operation) { + String id = String.format("%016x", next.incrementAndGet()); + String previous = active.get(); + active.set(id); + return new Scope() { + private boolean ended; + private boolean detached; + @Override public SqlExecutionCorrelation correlation() { return new SqlExecutionCorrelation("00000000000000000000000000000001", id); } + @Override public void success(Map attrs) { finish("success"); } + @Override public void failure(Throwable error) { finish("failure"); } + @Override public void detach() { if (!detached) { active.set(previous); detached = true; } } + @Override public PropagationScope activate() { + String before = active.get(); active.set(id); return () -> active.set(before); + } + private void finish(String result) { if (!ended) { ended = true; completed.add(result); detach(); } } + }; + } + } +} diff --git a/teaql-db2/pom.xml b/teaql-db2/pom.xml index b6f95893..d4886a65 100644 --- a/teaql-db2/pom.xml +++ b/teaql-db2/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-db2 teaql-db2 diff --git a/teaql-dm8/pom.xml b/teaql-dm8/pom.xml index db7d1c65..dcb69493 100644 --- a/teaql-dm8/pom.xml +++ b/teaql-dm8/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-dm8 teaql-dm8 diff --git a/teaql-duckdb/pom.xml b/teaql-duckdb/pom.xml index ec5de021..8b666852 100644 --- a/teaql-duckdb/pom.xml +++ b/teaql-duckdb/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-duckdb teaql-duckdb diff --git a/teaql-dynamic-fields-api/pom.xml b/teaql-dynamic-fields-api/pom.xml index 193fd567..0bb67d3b 100644 --- a/teaql-dynamic-fields-api/pom.xml +++ b/teaql-dynamic-fields-api/pom.xml @@ -7,7 +7,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-dynamic-fields-jdbc/pom.xml b/teaql-dynamic-fields-jdbc/pom.xml index d648741a..0360a556 100644 --- a/teaql-dynamic-fields-jdbc/pom.xml +++ b/teaql-dynamic-fields-jdbc/pom.xml @@ -7,7 +7,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-hana/pom.xml b/teaql-hana/pom.xml index de24c117..0b2e6465 100644 --- a/teaql-hana/pom.xml +++ b/teaql-hana/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-hana teaql-hana diff --git a/teaql-jackson/pom.xml b/teaql-jackson/pom.xml index 22f6da68..27cfceb2 100644 --- a/teaql-jackson/pom.xml +++ b/teaql-jackson/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-jackson diff --git a/teaql-mssql/pom.xml b/teaql-mssql/pom.xml index 6191e367..840aa68b 100644 --- a/teaql-mssql/pom.xml +++ b/teaql-mssql/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-mssql teaql-mssql diff --git a/teaql-mysql/pom.xml b/teaql-mysql/pom.xml index ba415521..282186e2 100644 --- a/teaql-mysql/pom.xml +++ b/teaql-mysql/pom.xml @@ -7,7 +7,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-mysql diff --git a/teaql-opentelemetry/pom.xml b/teaql-opentelemetry/pom.xml index 9ba310db..98ba7eee 100644 --- a/teaql-opentelemetry/pom.xml +++ b/teaql-opentelemetry/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-opentelemetry @@ -15,6 +15,13 @@ + + + org.jetbrains.kotlin + kotlin-stdlib + 2.4.20 + io.opentelemetry opentelemetry-bom @@ -37,7 +44,6 @@ io.opentelemetry opentelemetry-sdk - test io.opentelemetry @@ -47,7 +53,12 @@ io.opentelemetry opentelemetry-exporter-otlp - test + + + io.opentelemetry.proto + opentelemetry-proto + + 1.11.0-alpha junit diff --git a/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/BoundedSignalAdmission.java b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/BoundedSignalAdmission.java new file mode 100644 index 00000000..83b841c5 --- /dev/null +++ b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/BoundedSignalAdmission.java @@ -0,0 +1,83 @@ +package io.teaql.opentelemetry; + +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.LogRecordProcessor; +import io.opentelemetry.sdk.logs.ReadWriteLogRecord; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.ReadWriteSpan; +import io.opentelemetry.sdk.trace.SpanProcessor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** Bounds queued plus exporting records before the SDK's equal-size queue. */ +final class BoundedSignalAdmission { + private final int capacity; + private final ExportLossAccounting losses; + private final ExportLossAccounting.Signal signal; + private final AtomicInteger pending = new AtomicInteger(); + private final AtomicBoolean closed = new AtomicBoolean(); + + BoundedSignalAdmission(int capacity, ExportLossAccounting losses, ExportLossAccounting.Signal signal) { + if (capacity < 1 || signal == ExportLossAccounting.Signal.METRICS) throw new IllegalArgumentException("Invalid event queue"); + this.capacity = capacity; this.losses = losses; this.signal = signal; + } + + int pending() { return pending.get(); } + + boolean acquire() { + while (!closed.get()) { + int current = pending.get(); + if (current >= capacity) { + losses.lost(signal, ExportLossAccounting.Reason.QUEUE_OVERFLOW, 1); + return false; + } + if (pending.compareAndSet(current, current + 1)) return true; + } + losses.lost(signal, ExportLossAccounting.Reason.SHUTDOWN, 1); + return false; + } + + void release(int size) { pending.addAndGet(-size); } + + private void rejected() { + release(1); + losses.lost(signal, ExportLossAccounting.Reason.EXPORT_FAILED, 1); + } + + SpanProcessor spans(SpanProcessor delegate) { + return new SpanProcessor() { + @Override public void onStart(Context parent, ReadWriteSpan span) { delegate.onStart(parent, span); } + @Override public boolean isStartRequired() { return delegate.isStartRequired(); } + @Override public boolean isEndRequired() { return true; } + @Override public void onEnd(ReadableSpan span) { + if (!span.getSpanContext().isSampled()) return; + // Serialize only the non-blocking queue handoff with shutdown. + // Never hold this monitor while waiting for export completion. + synchronized (BoundedSignalAdmission.this) { + if (!acquire()) return; + try { delegate.onEnd(span); } catch (Throwable ignored) { rejected(); } + } + } + @Override public CompletableResultCode forceFlush() { return delegate.forceFlush(); } + @Override public CompletableResultCode shutdown() { + synchronized (BoundedSignalAdmission.this) { closed.set(true); return delegate.shutdown(); } + } + }; + } + + LogRecordProcessor logs(LogRecordProcessor delegate) { + return new LogRecordProcessor() { + @Override public void onEmit(Context context, ReadWriteLogRecord record) { + synchronized (BoundedSignalAdmission.this) { + if (!acquire()) return; + try { delegate.onEmit(context, record); } catch (Throwable ignored) { rejected(); } + } + } + @Override public CompletableResultCode forceFlush() { return delegate.forceFlush(); } + @Override public CompletableResultCode shutdown() { + synchronized (BoundedSignalAdmission.this) { closed.set(true); return delegate.shutdown(); } + } + }; + } +} diff --git a/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/ExportLossAccounting.java b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/ExportLossAccounting.java new file mode 100644 index 00000000..b23ea813 --- /dev/null +++ b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/ExportLossAccounting.java @@ -0,0 +1,125 @@ +package io.teaql.opentelemetry; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import io.opentelemetry.sdk.common.export.MemoryMode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.teaql.runtime.TelemetryStartup; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; + +/** Counts failed batch entries, not guessed delivered records or retained retries. */ +final class ExportLossAccounting { + enum Signal { TRACES, LOGS, METRICS } + enum Reason { EXPORT_FAILED, QUEUE_OVERFLOW, SHUTDOWN } + private final AtomicLong[][] lost = new AtomicLong[Signal.values().length][Reason.values().length]; + private final AtomicBoolean[][] announced = new AtomicBoolean[Signal.values().length][Reason.values().length]; + private final OtlpResponseAccounting responseAccounting = new OtlpResponseAccounting(); + + OtlpResponseAccounting responses() { return responseAccounting; } + + ExportLossAccounting() { + for (Signal signal : Signal.values()) for (Reason reason : Reason.values()) { + lost[signal.ordinal()][reason.ordinal()] = new AtomicLong(); + announced[signal.ordinal()][reason.ordinal()] = new AtomicBoolean(); + } + } + + long lost(Signal signal) { + long total = 0; + for (Reason reason : Reason.values()) total += lost(signal, reason); + return total; + } + + long lost(Signal signal, Reason reason) { return lost[signal.ordinal()][reason.ordinal()].get(); } + + void lost(Signal signal, Reason reason, int size) { + if (size <= 0) return; + lost[signal.ordinal()][reason.ordinal()].addAndGet(size); + if (announced[signal.ordinal()][reason.ordinal()].compareAndSet(false, true)) TelemetryStartup.notice( + "TeaQL telemetry: export entries lost; signal=" + signal.name().toLowerCase(java.util.Locale.ROOT) + + "; reason=" + reason.name().toLowerCase(java.util.Locale.ROOT) + + "; see teaql.telemetry.export.lost; business outcome unchanged."); + } + + void bind(Meter meter) { + responseAccounting.bind(meter); + meter.counterBuilder("teaql.telemetry.export.lost") + .setDescription("Failed export entries; metrics count MetricData objects, not points") + .setUnit("{entry}").buildWithCallback(observation -> { + for (Signal signal : Signal.values()) for (Reason reason : Reason.values()) { + // Metrics collect cumulative snapshots, not a span/log event queue. + if (signal == Signal.METRICS && reason != Reason.EXPORT_FAILED) continue; + observation.record(lost(signal, reason), Attributes.builder() + .put("teaql.telemetry.signal", signal.name().toLowerCase(java.util.Locale.ROOT)) + .put("teaql.telemetry.loss.reason", reason.name().toLowerCase(java.util.Locale.ROOT)).build()); + } + }); + } + + private CompletableResultCode watch(Signal signal, int size, Supplier export, + BoundedSignalAdmission admission) { + CompletableResultCode result; + try { result = export.get(); } + catch (Throwable ignored) { result = CompletableResultCode.ofFailure(); } + if (result == null) result = CompletableResultCode.ofFailure(); + CompletableResultCode outcome = result; + outcome.whenComplete(() -> { + if (admission != null) admission.release(size); + if (!outcome.isSuccess()) lost(signal, Reason.EXPORT_FAILED, size); + }); + return outcome; + } + + SpanExporter spans(SpanExporter delegate) { + return spans(delegate, null); + } + + SpanExporter spans(SpanExporter delegate, BoundedSignalAdmission admission) { + return new SpanExporter() { + @Override public CompletableResultCode export(Collection values) { + return watch(Signal.TRACES, values.size(), () -> delegate.export(values), admission); + } + @Override public CompletableResultCode flush() { return delegate.flush(); } + @Override public CompletableResultCode shutdown() { return delegate.shutdown(); } + }; + } + + LogRecordExporter logs(LogRecordExporter delegate) { + return logs(delegate, null); + } + + LogRecordExporter logs(LogRecordExporter delegate, BoundedSignalAdmission admission) { + return new LogRecordExporter() { + @Override public CompletableResultCode export(Collection values) { + return watch(Signal.LOGS, values.size(), () -> delegate.export(values), admission); + } + @Override public CompletableResultCode flush() { return delegate.flush(); } + @Override public CompletableResultCode shutdown() { return delegate.shutdown(); } + }; + } + + MetricExporter metrics(MetricExporter delegate) { + return new MetricExporter() { + @Override public CompletableResultCode export(Collection values) { + return watch(Signal.METRICS, values.size(), () -> delegate.export(values), null); + } + @Override public AggregationTemporality getAggregationTemporality(InstrumentType type) { return delegate.getAggregationTemporality(type); } + @Override public Aggregation getDefaultAggregation(InstrumentType type) { return delegate.getDefaultAggregation(type); } + @Override public MemoryMode getMemoryMode() { return delegate.getMemoryMode(); } + @Override public CompletableResultCode flush() { return delegate.flush(); } + @Override public CompletableResultCode shutdown() { return delegate.shutdown(); } + }; + } +} diff --git a/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/OpenTelemetryRuntimeTelemetry.java b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/OpenTelemetryRuntimeTelemetry.java index 96599432..cc1bb1a0 100644 --- a/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/OpenTelemetryRuntimeTelemetry.java +++ b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/OpenTelemetryRuntimeTelemetry.java @@ -8,6 +8,9 @@ import io.opentelemetry.api.logs.Logger; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.GlobalOpenTelemetry; @@ -100,9 +103,23 @@ public Scope start(Operation operation) { Span span = tracer.spanBuilder("teaql." + operation.family()) .setAllAttributes(attributes(operation.attributes())) .startSpan(); - io.opentelemetry.context.Scope activation = span.makeCurrent(); + Context statementContext = Context.current().with(span); + io.opentelemetry.context.Scope initialActivation = statementContext.makeCurrent(); return new Scope() { private boolean ended; + private io.opentelemetry.context.Scope activation = initialActivation; + + @Override public io.teaql.core.SqlExecutionCorrelation correlation() { + var identity = span.getSpanContext(); + return identity.isValid() ? new io.teaql.core.SqlExecutionCorrelation(identity.getTraceId(), identity.getSpanId()) : null; + } + @Override public synchronized void detach() { + if (activation != null) { activation.close(); activation = null; } + } + @Override public PropagationScope activate() { + var scope = statementContext.makeCurrent(); + return scope::close; + } @Override public synchronized void success(Map completion) { @@ -145,6 +162,7 @@ private void finish(String outcome, String errorCategory) { operations.add(1, dimensions); if (logger != null) { var record = logger.logRecordBuilder() + .setContext(statementContext) .setSeverity(Severity.INFO) .setSeverityText("INFO") .setBody("TeaQL runtime operation completed") @@ -157,12 +175,25 @@ var record = logger.logRecordBuilder() } record.emit(); } - activation.close(); + detach(); span.end(); } }; } + @Override public boolean recordsSqlDiagnostics() { return logger != null; } + + @Override public void recordDiagnostic(Diagnostic event) { + if (logger == null || event == null || event.correlation() == null) return; + var identity = event.correlation(); + SpanContext span = SpanContext.create(identity.traceId(), identity.spanId(), TraceFlags.getSampled(), TraceState.getDefault()); + if (!span.isValid()) return; + // A late active span is unrelated. The provider carried this exact identity from execution start. + logger.logRecordBuilder().setContext(Context.root().with(Span.wrap(span))) + .setSeverity(Severity.INFO).setSeverityText("INFO").setBody("TeaQL SQL execution") + .setAllAttributes(attributes(event.attributes())).emit(); + } + @Override public void flush() { flush.run(); diff --git a/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/OtlpResponseAccounting.java b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/OtlpResponseAccounting.java new file mode 100644 index 00000000..71ee0d5c --- /dev/null +++ b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/OtlpResponseAccounting.java @@ -0,0 +1,103 @@ +package io.teaql.opentelemetry; + +import com.google.protobuf.CodedInputStream; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; +import io.teaql.runtime.TelemetryStartup; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +/** Receiver-reported rejection is separate from local failed batch-entry units. */ +final class OtlpResponseAccounting { + static final int MAX_RESPONSE_BYTES = 4 * 1024 * 1024; + enum State { PARTIAL_SUCCESS, WARNING, INVALID_RESPONSE } + private final AtomicLong[] rejected = { new AtomicLong(), new AtomicLong(), new AtomicLong() }; + private final AtomicLong[][] responses = new AtomicLong[3][State.values().length]; + private final AtomicBoolean[][] announced = new AtomicBoolean[3][State.values().length]; + + OtlpResponseAccounting() { + for (var signal : ExportLossAccounting.Signal.values()) for (var state : State.values()) { + responses[signal.ordinal()][state.ordinal()] = new AtomicLong(); + announced[signal.ordinal()][state.ordinal()] = new AtomicBoolean(); + } + } + + long rejected(ExportLossAccounting.Signal signal) { return rejected[signal.ordinal()].get(); } + long responses(ExportLossAccounting.Signal signal, State state) { return responses[signal.ordinal()][state.ordinal()].get(); } + + boolean receive(ExportLossAccounting.Signal signal, byte[] body) { + try { + // An empty protobuf response has no encoded fields; some senders + // represent that valid empty body as null. + if (body == null) body = new byte[0]; + if (body.length > MAX_RESPONSE_BYTES) throw new IOException(); + CodedInputStream input = CodedInputStream.newInstance(body); + input.setSizeLimit(MAX_RESPONSE_BYTES); input.setRecursionLimit(16); + boolean partial; boolean warning; long count; + switch (signal) { + case TRACES -> { + var reply = ExportTraceServiceResponse.parseFrom(input); + partial = reply.hasPartialSuccess(); count = reply.getPartialSuccess().getRejectedSpans(); + warning = !reply.getPartialSuccess().getErrorMessageBytes().isEmpty(); + } + case LOGS -> { + var reply = ExportLogsServiceResponse.parseFrom(input); + partial = reply.hasPartialSuccess(); count = reply.getPartialSuccess().getRejectedLogRecords(); + warning = !reply.getPartialSuccess().getErrorMessageBytes().isEmpty(); + } + case METRICS -> { + var reply = ExportMetricsServiceResponse.parseFrom(input); + partial = reply.hasPartialSuccess(); count = reply.getPartialSuccess().getRejectedDataPoints(); + warning = !reply.getPartialSuccess().getErrorMessageBytes().isEmpty(); + } + default -> throw new IOException(); + } + if (!input.isAtEnd() || count < 0) throw new IOException(); + // Collector 0.159 encodes a default empty partial_success (0a00) + // on full acceptance. Only actual rejection or diagnostic text is + // actionable; never manufacture a warning from an empty wrapper. + if (partial && (count > 0 || warning)) { + if (count > 0) rejected[signal.ordinal()].updateAndGet(value -> + value > Long.MAX_VALUE - count ? Long.MAX_VALUE : value + count); + record(signal, count > 0 ? State.PARTIAL_SUCCESS : State.WARNING); + } + return true; + } catch (Throwable ignored) { + record(signal, State.INVALID_RESPONSE); + return false; + } + } + + void invalid(ExportLossAccounting.Signal signal) { record(signal, State.INVALID_RESPONSE); } + + private void record(ExportLossAccounting.Signal signal, State state) { + responses[signal.ordinal()][state.ordinal()].incrementAndGet(); + if (announced[signal.ordinal()][state.ordinal()].compareAndSet(false, true)) TelemetryStartup.notice( + "TeaQL telemetry: receiver response; signal=" + signal.name().toLowerCase(java.util.Locale.ROOT) + + "; state=" + state.name().toLowerCase(java.util.Locale.ROOT) + + "; remote details masked; see teaql.telemetry.export.rejected and teaql.telemetry.export.response; business outcome unchanged."); + } + + void bind(Meter meter) { + meter.counterBuilder("teaql.telemetry.export.rejected") + .setDescription("Receiver-reported rejected spans, log records or metric data points; not failed MetricData objects") + .setUnit("{item}").buildWithCallback(observer -> { + for (var signal : ExportLossAccounting.Signal.values()) observer.record(rejected(signal), + Attributes.builder().put("teaql.telemetry.signal", signal.name().toLowerCase(java.util.Locale.ROOT)) + .put("teaql.telemetry.rejected.unit", signal == ExportLossAccounting.Signal.METRICS + ? "data_point" : signal == ExportLossAccounting.Signal.TRACES ? "span" : "log_record").build()); + }); + meter.counterBuilder("teaql.telemetry.export.response").setUnit("{response}") + .setDescription("Non-full OTLP acknowledgements; no remote body or message labels") + .buildWithCallback(observer -> { + for (var signal : ExportLossAccounting.Signal.values()) for (var state : State.values()) + observer.record(responses(signal, state), Attributes.builder() + .put("teaql.telemetry.signal", signal.name().toLowerCase(java.util.Locale.ROOT)) + .put("teaql.telemetry.response.state", state.name().toLowerCase(java.util.Locale.ROOT)).build()); + }); + } +} diff --git a/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/OwnedHttpComponents.java b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/OwnedHttpComponents.java new file mode 100644 index 00000000..81bf5416 --- /dev/null +++ b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/OwnedHttpComponents.java @@ -0,0 +1,104 @@ +package io.teaql.opentelemetry; + +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.HttpResponse; +import io.opentelemetry.sdk.common.export.HttpSender; +import io.opentelemetry.sdk.common.export.HttpSenderProvider; +import io.opentelemetry.sdk.common.export.HttpSenderConfig; +import io.opentelemetry.sdk.common.export.MessageWriter; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +/** Instance-local SDK transport boundary: never changes global SDK/JUL settings. */ +final class OwnedHttpComponents implements ComponentLoader { + private final ComponentLoader delegate; + private final OtlpResponseAccounting responses; + + OwnedHttpComponents(ClassLoader loader, OtlpResponseAccounting responses) { + delegate = ComponentLoader.forClassLoader(loader); this.responses = responses; + } + + @Override public Iterable load(Class type) { + if (type != HttpSenderProvider.class) return delegate.load(type); + HttpSenderProvider provider = ComponentLoader.loadList(delegate, HttpSenderProvider.class) + .stream().findFirst().orElseThrow(() -> new IllegalStateException("HTTP sender unavailable")); + HttpSenderProvider safe = config -> protect(provider.createSender(bounded(config)), + responses, signal(config.getEndpoint().getPath())); + return List.of(type.cast(safe)); + } + + static HttpSender protect(HttpSender delegate) { + return protect(delegate, null, null); + } + + static HttpSender protect(HttpSender delegate, OtlpResponseAccounting responses, ExportLossAccounting.Signal signal) { + return new HttpSender() { + @Override public void send(MessageWriter message, Consumer response, Consumer error) { + AtomicBoolean completed = new AtomicBoolean(); + Consumer safeError = ignored -> { + if (completed.compareAndSet(false, true)) error.accept(new SafeTransportFailure()); + }; + try { + delegate.send(message, actual -> { + int status; + try { status = actual.getStatusCode(); } + catch (Throwable ignored) { safeError.accept(null); return; } + if (!completed.compareAndSet(false, true)) return; + // Decode successful acknowledgements before the SDK drops their + // partial-success fields. Error bodies/reasons remain unread. + if (status >= 200 && status < 300 && responses != null) { + boolean valid; + try { + if (status != 200) { responses.invalid(signal); valid = false; } + else valid = responses.receive(signal, actual.getResponseBody()); + } + catch (Throwable ignored) { responses.invalid(signal); valid = false; } + if (!valid) { error.accept(new SafeTransportFailure()); return; } + } + final int code = status >= 100 && status <= 599 ? status : 0; + response.accept(new HttpResponse() { + @Override public int getStatusCode() { return code; } + @Override public String getStatusMessage() { return "remote response masked"; } + @Override public byte[] getResponseBody() { return new byte[0]; } + }); + }, safeError); + } catch (Throwable ignored) { safeError.accept(null); } + } + + @Override public CompletableResultCode shutdown() { + try { return delegate.shutdown(); } + catch (Throwable ignored) { return CompletableResultCode.ofFailure(); } + } + }; + } + + private static ExportLossAccounting.Signal signal(String path) { + if (path.endsWith("/v1/traces")) return ExportLossAccounting.Signal.TRACES; + if (path.endsWith("/v1/logs")) return ExportLossAccounting.Signal.LOGS; + if (path.endsWith("/v1/metrics")) return ExportLossAccounting.Signal.METRICS; + throw new IllegalArgumentException("Unknown owned OTLP signal"); + } + + static HttpSenderConfig bounded(HttpSenderConfig source) { + return new HttpSenderConfig() { + public java.net.URI getEndpoint() { return source.getEndpoint(); } + public String getContentType() { return source.getContentType(); } + public io.opentelemetry.sdk.common.export.Compressor getCompressor() { return source.getCompressor(); } + public java.time.Duration getTimeout() { return source.getTimeout(); } + public java.time.Duration getConnectTimeout() { return source.getConnectTimeout(); } + public java.util.function.Supplier>> getHeadersSupplier() { return source.getHeadersSupplier(); } + public io.opentelemetry.sdk.common.export.ProxyOptions getProxyOptions() { return source.getProxyOptions(); } + public io.opentelemetry.sdk.common.export.RetryPolicy getRetryPolicy() { return source.getRetryPolicy(); } + public javax.net.ssl.SSLContext getSslContext() { return source.getSslContext(); } + public javax.net.ssl.X509TrustManager getTrustManager() { return source.getTrustManager(); } + public java.util.concurrent.ExecutorService getExecutorService() { return source.getExecutorService(); } + public long getMaxResponseBodySize() { return Math.min(source.getMaxResponseBodySize(), OtlpResponseAccounting.MAX_RESPONSE_BYTES); } + }; + } + + private static final class SafeTransportFailure extends RuntimeException { + SafeTransportFailure() { super("TeaQL OTLP transport failed; details masked", null, false, false); } + } +} diff --git a/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/StandardOpenTelemetryFactory.java b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/StandardOpenTelemetryFactory.java new file mode 100644 index 00000000..c88b6fa7 --- /dev/null +++ b/teaql-opentelemetry/src/main/java/io/teaql/opentelemetry/StandardOpenTelemetryFactory.java @@ -0,0 +1,81 @@ +package io.teaql.opentelemetry; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.samplers.Sampler; +import io.teaql.runtime.RuntimeTelemetry; +import io.teaql.runtime.StandardTelemetryFactory; +import io.teaql.runtime.TelemetryStartup; +import java.net.URI; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** Builds an owned pipeline without registering a GlobalOpenTelemetry instance. */ +public final class StandardOpenTelemetryFactory implements StandardTelemetryFactory { + @Override + public RuntimeTelemetry create(String serviceName, URI endpoint) { + return create(serviceName, endpoint, new ExportLossAccounting()); + } + + RuntimeTelemetry create(String serviceName, URI endpoint, ExportLossAccounting losses) { + return create(serviceName, endpoint, losses, 2048); + } + + RuntimeTelemetry create(String serviceName, URI endpoint, ExportLossAccounting losses, int queueCapacity) { + String base = endpoint.toString().replaceAll("/$", ""); + var components = new OwnedHttpComponents(StandardOpenTelemetryFactory.class.getClassLoader(), losses.responses()); + // SDK 1.65 uses null to disable its HTTP retry policy; verify with actual + // failing HTTP requests rather than accepting the default retry behavior. + Resource resource = Resource.create(Attributes.builder().put("service.name", serviceName) + .put("teaql.runtime.language", "java").build()); + var traceAdmission = new BoundedSignalAdmission(queueCapacity, losses, ExportLossAccounting.Signal.TRACES); + var logAdmission = new BoundedSignalAdmission(queueCapacity, losses, ExportLossAccounting.Signal.LOGS); + SdkTracerProvider traces = SdkTracerProvider.builder().setResource(resource).setSampler(Sampler.alwaysOn()) + .addSpanProcessor(traceAdmission.spans(BatchSpanProcessor.builder(losses.spans(OtlpHttpSpanExporter.builder() + .setEndpoint(base + "/v1/traces").setComponentLoader(components).setRetryPolicy(null) + .setConnectTimeout(Duration.ofSeconds(3)).setTimeout(Duration.ofSeconds(3)).build(), traceAdmission)) + .setMaxQueueSize(queueCapacity).setMaxExportBatchSize(Math.min(128, queueCapacity)) + .setExporterTimeout(Duration.ofSeconds(5)).build())).build(); + SdkMeterProvider metrics = SdkMeterProvider.builder().setResource(resource) + .registerMetricReader(PeriodicMetricReader.builder(losses.metrics(OtlpHttpMetricExporter.builder() + .setEndpoint(base + "/v1/metrics").setComponentLoader(components).setRetryPolicy(null) + .setConnectTimeout(Duration.ofSeconds(3)).setTimeout(Duration.ofSeconds(3)).build())) + .setInterval(Duration.ofSeconds(10)).build()).build(); + losses.bind(metrics.get("io.teaql.runtime")); + SdkLoggerProvider logs = SdkLoggerProvider.builder().setResource(resource) + .addLogRecordProcessor(logAdmission.logs(BatchLogRecordProcessor.builder(losses.logs(OtlpHttpLogRecordExporter.builder() + .setEndpoint(base + "/v1/logs").setComponentLoader(components).setRetryPolicy(null) + .setConnectTimeout(Duration.ofSeconds(3)).setTimeout(Duration.ofSeconds(3)).build(), logAdmission)) + .setMaxQueueSize(queueCapacity).setMaxExportBatchSize(Math.min(128, queueCapacity)) + .setExporterTimeout(Duration.ofSeconds(5)).build())).build(); + AtomicBoolean closed = new AtomicBoolean(); + Runnable flush = () -> { + var result = io.opentelemetry.sdk.common.CompletableResultCode.ofAll(java.util.List.of( + traces.forceFlush(), logs.forceFlush(), metrics.forceFlush())); + if (!result.join(6, TimeUnit.SECONDS).isSuccess()) TelemetryStartup.notice( + "TeaQL telemetry: export incomplete; business outcome unchanged."); + }; + Runnable shutdown = () -> { + if (!closed.compareAndSet(false, true)) return; + var result = io.opentelemetry.sdk.common.CompletableResultCode.ofAll(java.util.List.of( + traces.shutdown(), logs.shutdown(), metrics.shutdown())); + if (!result.join(6, TimeUnit.SECONDS).isSuccess()) TelemetryStartup.notice( + "TeaQL telemetry: owned shutdown incomplete; business outcome unchanged."); + }; + return new OpenTelemetryRuntimeTelemetry(traces.get("io.teaql.runtime"), + metrics.get("io.teaql.runtime"), logs.get("io.teaql.runtime"), flush, shutdown, + ContextPropagators.create(W3CTraceContextPropagator.getInstance())); + } +} diff --git a/teaql-opentelemetry/src/main/resources/META-INF/services/io.teaql.runtime.StandardTelemetryFactory b/teaql-opentelemetry/src/main/resources/META-INF/services/io.teaql.runtime.StandardTelemetryFactory new file mode 100644 index 00000000..e8f04387 --- /dev/null +++ b/teaql-opentelemetry/src/main/resources/META-INF/services/io.teaql.runtime.StandardTelemetryFactory @@ -0,0 +1 @@ +io.teaql.opentelemetry.StandardOpenTelemetryFactory diff --git a/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/BoundedSignalAdmissionTest.java b/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/BoundedSignalAdmissionTest.java new file mode 100644 index 00000000..1de9c5f9 --- /dev/null +++ b/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/BoundedSignalAdmissionTest.java @@ -0,0 +1,177 @@ +package io.teaql.opentelemetry; + +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.LogRecordProcessor; +import io.opentelemetry.sdk.logs.ReadWriteLogRecord; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.time.Duration; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +public class BoundedSignalAdmissionTest { + @Test public void actualOwnedFactoryAccountsOverflowAndRecoversThroughHttp() throws Exception { + var server = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + var workers = Executors.newFixedThreadPool(3); + CountDownLatch release = new CountDownLatch(1); + AtomicInteger received = new AtomicInteger(); + server.setExecutor(workers); + server.createContext("/", exchange -> { + exchange.getRequestBody().readAllBytes(); + try { + if (!release.await(2, TimeUnit.SECONDS)) { exchange.sendResponseHeaders(503, -1); return; } + received.incrementAndGet(); exchange.sendResponseHeaders(200, -1); + } catch (InterruptedException interrupted) { Thread.currentThread().interrupt(); } + finally { exchange.close(); } + }); + server.start(); + var losses = new ExportLossAccounting(); + io.teaql.runtime.RuntimeTelemetry telemetry = null; + try { + // Same factory assembly as production; only a package-private test + // capacity differs. No second queue setting is exposed to applications. + telemetry = new StandardOpenTelemetryFactory().create("owned-overflow-test", + URI.create("http://127.0.0.1:" + server.getAddress().getPort()), losses, 8); + for (int i = 0; i < 24; i++) telemetry.start(new io.teaql.runtime.RuntimeTelemetry.Operation( + "query", "School.list", Map.of())).success(); + assertEquals(16, losses.lost(ExportLossAccounting.Signal.TRACES, ExportLossAccounting.Reason.QUEUE_OVERFLOW)); + assertEquals(16, losses.lost(ExportLossAccounting.Signal.LOGS, ExportLossAccounting.Reason.QUEUE_OVERFLOW)); + release.countDown(); telemetry.flush(); + assertEquals(0, losses.lost(ExportLossAccounting.Signal.TRACES, ExportLossAccounting.Reason.EXPORT_FAILED)); + assertEquals(0, losses.lost(ExportLossAccounting.Signal.LOGS, ExportLossAccounting.Reason.EXPORT_FAILED)); + assertEquals(0, losses.lost(ExportLossAccounting.Signal.METRICS)); + assertTrue(received.get() >= 3); + telemetry.start(new io.teaql.runtime.RuntimeTelemetry.Operation("query", "School.list", Map.of())).success(); + telemetry.flush(); + assertEquals(16, losses.lost(ExportLossAccounting.Signal.TRACES)); + assertEquals(16, losses.lost(ExportLossAccounting.Signal.LOGS)); + } finally { + release.countDown(); if (telemetry != null) telemetry.shutdown(); server.stop(0); workers.shutdownNow(); + } + } + + @Test public void realSdkTraceQueueCountsInFlightAndOverflowThenReusesCapacity() throws Exception { + var losses = new ExportLossAccounting(); + var admission = new BoundedSignalAdmission(4, losses, ExportLossAccounting.Signal.TRACES); + var block = new BlockedBatch(); + var delegate = new SpanExporter() { + public CompletableResultCode export(Collection values) { return block.export(values.size()); } + public CompletableResultCode flush() { return CompletableResultCode.ofSuccess(); } + public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } + }; + var processor = admission.spans(BatchSpanProcessor.builder(losses.spans(delegate, admission)) + .setMaxQueueSize(4).setMaxExportBatchSize(1).setScheduleDelay(Duration.ofMillis(1)) + .setExporterTimeout(Duration.ofSeconds(3)).build()); + try (var provider = SdkTracerProvider.builder().addSpanProcessor(processor).build()) { + var tracer = provider.get("io.teaql.runtime"); + tracer.spanBuilder("first").startSpan().end(); + assertTrue(block.entered.await(2, TimeUnit.SECONDS)); + for (int i = 0; i < 6; i++) tracer.spanBuilder("next").startSpan().end(); + assertEquals(4, admission.pending()); + assertEquals(3, losses.lost(ExportLossAccounting.Signal.TRACES, ExportLossAccounting.Reason.QUEUE_OVERFLOW)); + assertEquals(0, losses.lost(ExportLossAccounting.Signal.TRACES, ExportLossAccounting.Reason.EXPORT_FAILED)); + block.completion.succeed(); + assertTrue(provider.forceFlush().join(2, TimeUnit.SECONDS).isSuccess()); + assertEquals(0, admission.pending()); assertEquals(4, block.exported.get()); + tracer.spanBuilder("after recovery").startSpan().end(); + assertTrue(provider.forceFlush().join(2, TimeUnit.SECONDS).isSuccess()); + assertEquals(5, block.exported.get()); assertEquals(0, admission.pending()); + assertEquals(3, losses.lost(ExportLossAccounting.Signal.TRACES)); + } finally { block.completion.succeed(); } + } + + @Test public void realSdkLogQueueCountsInFlightAndOverflowThenReusesCapacity() throws Exception { + var losses = new ExportLossAccounting(); + var admission = new BoundedSignalAdmission(4, losses, ExportLossAccounting.Signal.LOGS); + var block = new BlockedBatch(); + var delegate = new LogRecordExporter() { + public CompletableResultCode export(Collection values) { return block.export(values.size()); } + public CompletableResultCode flush() { return CompletableResultCode.ofSuccess(); } + public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } + }; + var processor = admission.logs(BatchLogRecordProcessor.builder(losses.logs(delegate, admission)) + .setMaxQueueSize(4).setMaxExportBatchSize(1).setScheduleDelay(Duration.ofMillis(1)) + .setExporterTimeout(Duration.ofSeconds(3)).build()); + try (var provider = SdkLoggerProvider.builder().addLogRecordProcessor(processor).build()) { + var logger = provider.get("io.teaql.runtime"); + logger.logRecordBuilder().setBody("first").emit(); + assertTrue(block.entered.await(2, TimeUnit.SECONDS)); + for (int i = 0; i < 6; i++) logger.logRecordBuilder().setBody("next").emit(); + assertEquals(4, admission.pending()); + assertEquals(3, losses.lost(ExportLossAccounting.Signal.LOGS, ExportLossAccounting.Reason.QUEUE_OVERFLOW)); + block.completion.succeed(); + assertTrue(provider.forceFlush().join(2, TimeUnit.SECONDS).isSuccess()); + assertEquals(0, admission.pending()); assertEquals(4, block.exported.get()); + logger.logRecordBuilder().setBody("after recovery").emit(); + assertTrue(provider.forceFlush().join(2, TimeUnit.SECONDS).isSuccess()); + assertEquals(5, block.exported.get()); assertEquals(0, admission.pending()); + assertEquals(3, losses.lost(ExportLossAccounting.Signal.LOGS)); + } finally { block.completion.succeed(); } + } + + @Test public void concurrentAdmissionNeverExceedsCapacityAndReportsEveryRejectedEntry() throws Exception { + var losses = new ExportLossAccounting(); + var admission = new BoundedSignalAdmission(8, losses, ExportLossAccounting.Signal.TRACES); + var pool = Executors.newFixedThreadPool(8); + try { + List> tasks = new ArrayList<>(); + for (int i = 0; i < 32; i++) tasks.add(admission::acquire); + int accepted = 0; + for (var result : pool.invokeAll(tasks)) if (result.get()) accepted++; + assertEquals(8, accepted); assertEquals(8, admission.pending()); + assertEquals(24, losses.lost(ExportLossAccounting.Signal.TRACES, ExportLossAccounting.Reason.QUEUE_OVERFLOW)); + admission.release(8); assertEquals(0, admission.pending()); + } finally { pool.shutdownNow(); } + } + + @Test public void failedExportReleasesCapacityAndShutdownRejectsWithoutHandoff() { + var losses = new ExportLossAccounting(); + var admission = new BoundedSignalAdmission(1, losses, ExportLossAccounting.Signal.LOGS); + assertTrue(admission.acquire()); + var delegate = new LogRecordExporter() { + public CompletableResultCode export(Collection values) { throw new IllegalStateException("PRIVATE_EXPORT_CANARY"); } + public CompletableResultCode flush() { return CompletableResultCode.ofSuccess(); } + public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } + }; + assertFalse(losses.logs(delegate, admission).export(java.util.Arrays.asList((LogRecordData) null)).isSuccess()); + assertEquals(0, admission.pending()); + assertEquals(1, losses.lost(ExportLossAccounting.Signal.LOGS, ExportLossAccounting.Reason.EXPORT_FAILED)); + AtomicInteger handedOff = new AtomicInteger(); + var processor = admission.logs(new LogRecordProcessor() { + public void onEmit(Context context, ReadWriteLogRecord record) { handedOff.incrementAndGet(); } + }); + processor.shutdown(); processor.onEmit(Context.root(), null); + assertEquals(0, handedOff.get()); assertEquals(0, admission.pending()); + assertEquals(1, losses.lost(ExportLossAccounting.Signal.LOGS, ExportLossAccounting.Reason.SHUTDOWN)); + } + + private static final class BlockedBatch { + final CountDownLatch entered = new CountDownLatch(1); + final CompletableResultCode completion = new CompletableResultCode(); + final AtomicBoolean first = new AtomicBoolean(true); + final AtomicInteger exported = new AtomicInteger(); + CompletableResultCode export(int size) { + exported.addAndGet(size); + if (first.compareAndSet(true, false)) { entered.countDown(); return completion; } + return CompletableResultCode.ofSuccess(); + } + } +} diff --git a/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/OtlpResponseAccountingTest.java b/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/OtlpResponseAccountingTest.java new file mode 100644 index 00000000..a1c5cdd1 --- /dev/null +++ b/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/OtlpResponseAccountingTest.java @@ -0,0 +1,204 @@ +package io.teaql.opentelemetry; + +import com.sun.net.httpserver.HttpServer; +import io.opentelemetry.proto.collector.logs.v1.ExportLogsPartialSuccess; +import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceResponse; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsPartialSuccess; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; +import io.opentelemetry.proto.collector.trace.v1.ExportTracePartialSuccess; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.HttpResponse; +import io.opentelemetry.sdk.common.export.HttpSender; +import io.opentelemetry.sdk.common.export.MessageWriter; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.teaql.runtime.RuntimeTelemetry; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import org.junit.Test; +import static org.junit.Assert.*; + +public class OtlpResponseAccountingTest { + private static final String SECRET = "PARTIAL_RESPONSE_CANARY_9243a11_password"; + + @Test public void officialSignalRepliesPreserveLongCountsAndPointUnits() { + var response = new OtlpResponseAccounting(); + assertTrue(response.receive(ExportLossAccounting.Signal.TRACES, reply(ExportLossAccounting.Signal.TRACES, 3_000_000_000L))); + assertTrue(response.receive(ExportLossAccounting.Signal.LOGS, reply(ExportLossAccounting.Signal.LOGS, 2))); + assertTrue(response.receive(ExportLossAccounting.Signal.METRICS, reply(ExportLossAccounting.Signal.METRICS, 5))); + assertEquals(3_000_000_000L, response.rejected(ExportLossAccounting.Signal.TRACES)); + assertEquals(2, response.rejected(ExportLossAccounting.Signal.LOGS)); + assertEquals(5, response.rejected(ExportLossAccounting.Signal.METRICS)); + var reader = InMemoryMetricReader.create(); + try (var metrics = SdkMeterProvider.builder().registerMetricReader(reader).build()) { + response.bind(metrics.get("io.teaql.runtime")); + var counter = reader.collectAllMetrics().stream().filter(value -> value.getName().equals("teaql.telemetry.export.rejected")).findFirst().orElseThrow(); + var point = counter.getLongSumData().getPoints().stream().filter(value -> "metrics".equals( + value.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("teaql.telemetry.signal")))).findFirst().orElseThrow(); + assertEquals(5, point.getValue()); + assertEquals("data_point", point.getAttributes().get(io.opentelemetry.api.common.AttributeKey.stringKey("teaql.telemetry.rejected.unit"))); + assertFalse(counter.toString().contains(SECRET)); + } + } + + @Test public void warningHasNoRejectionAndEmptyReplyIsFullSuccess() { + var response = new OtlpResponseAccounting(); + assertTrue(response.receive(ExportLossAccounting.Signal.TRACES, new byte[0])); + assertTrue(response.receive(ExportLossAccounting.Signal.TRACES, null)); + assertTrue(response.receive(ExportLossAccounting.Signal.TRACES, + ExportTraceServiceResponse.newBuilder().setPartialSuccess(ExportTracePartialSuccess.getDefaultInstance()).build().toByteArray())); + assertTrue(response.receive(ExportLossAccounting.Signal.LOGS, + ExportLogsServiceResponse.newBuilder().setPartialSuccess(ExportLogsPartialSuccess.getDefaultInstance()).build().toByteArray())); + assertTrue(response.receive(ExportLossAccounting.Signal.METRICS, + ExportMetricsServiceResponse.newBuilder().setPartialSuccess(ExportMetricsPartialSuccess.getDefaultInstance()).build().toByteArray())); + assertEquals(0, response.responses(ExportLossAccounting.Signal.TRACES, OtlpResponseAccounting.State.WARNING)); + assertTrue(response.receive(ExportLossAccounting.Signal.TRACES, reply(ExportLossAccounting.Signal.TRACES, 0))); + assertEquals(0, response.rejected(ExportLossAccounting.Signal.TRACES)); + assertEquals(1, response.responses(ExportLossAccounting.Signal.TRACES, OtlpResponseAccounting.State.WARNING)); + } + + @Test public void malformedNegativeOversizedAndDeepRepliesAreRejectedWithoutTextFallback() { + var response = new OtlpResponseAccounting(); + assertFalse(response.receive(ExportLossAccounting.Signal.LOGS, new byte[]{10, 20, 8})); + assertFalse(response.receive(ExportLossAccounting.Signal.LOGS, reply(ExportLossAccounting.Signal.LOGS, -1))); + assertFalse(response.receive(ExportLossAccounting.Signal.LOGS, new byte[OtlpResponseAccounting.MAX_RESPONSE_BYTES + 1])); + byte[] nestedGroups = new byte[64]; + java.util.Arrays.fill(nestedGroups, 0, 32, (byte) 11); java.util.Arrays.fill(nestedGroups, 32, 64, (byte) 12); + assertFalse(response.receive(ExportLossAccounting.Signal.LOGS, nestedGroups)); + assertEquals(4, response.responses(ExportLossAccounting.Signal.LOGS, OtlpResponseAccounting.State.INVALID_RESPONSE)); + assertEquals(0, response.rejected(ExportLossAccounting.Signal.LOGS)); + } + + @Test public void privateSenderAccountsOnePartialReplyDespiteDuplicateCallbacks() { + var response = new OtlpResponseAccounting(); AtomicInteger callbacks = new AtomicInteger(); + HttpSender nativeSender = new HttpSender() { + public void send(MessageWriter message, Consumer next, Consumer error) { + var value = http(200, reply(ExportLossAccounting.Signal.TRACES, 1)); next.accept(value); next.accept(value); + error.accept(new IllegalStateException(SECRET)); + } + public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } + }; + OwnedHttpComponents.protect(nativeSender, response, ExportLossAccounting.Signal.TRACES) + .send(null, safe -> { + callbacks.incrementAndGet(); assertEquals(200, safe.getStatusCode()); + assertEquals(0, safe.getResponseBody().length); assertFalse(safe.getStatusMessage().contains(SECRET)); + }, error -> fail("valid partial response is not a transport error")); + assertEquals(1, callbacks.get()); assertEquals(1, response.rejected(ExportLossAccounting.Signal.TRACES)); + } + + @Test public void invalidAcknowledgementCompletesOnceAsSafeFailureAndReleasesAdmission() { + var losses = new ExportLossAccounting(); + var admission = new BoundedSignalAdmission(1, losses, ExportLossAccounting.Signal.TRACES); + assertTrue(admission.acquire()); + var result = new CompletableResultCode(); + HttpSender nativeSender = new HttpSender() { + public void send(MessageWriter message, Consumer next, Consumer error) { + var invalid = http(200, SECRET.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + next.accept(invalid); next.accept(invalid); + } + public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } + }; + var sender = OwnedHttpComponents.protect(nativeSender, losses.responses(), ExportLossAccounting.Signal.TRACES); + var exporter = losses.spans(new io.opentelemetry.sdk.trace.export.SpanExporter() { + public CompletableResultCode export(java.util.Collection values) { + sender.send(null, response -> fail("malformed response cannot be full success"), failure -> { + assertFalse(failure.getMessage().contains(SECRET)); assertNull(failure.getCause()); result.fail(); + }); + return result; + } + public CompletableResultCode flush() { return CompletableResultCode.ofSuccess(); } + public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } + }, admission); + assertFalse(exporter.export(java.util.Arrays.asList((io.opentelemetry.sdk.trace.data.SpanData) null)).isSuccess()); + assertEquals(0, admission.pending()); + assertEquals(1, losses.lost(ExportLossAccounting.Signal.TRACES, ExportLossAccounting.Reason.EXPORT_FAILED)); + assertEquals(1, losses.responses().responses(ExportLossAccounting.Signal.TRACES, OtlpResponseAccounting.State.INVALID_RESPONSE)); + } + + @Test public void ownedSenderConfigurationCapsResponsesWithoutChangingSourceConfiguration() { + var source = new io.opentelemetry.sdk.common.export.HttpSenderConfig() { + public URI getEndpoint() { return URI.create("http://127.0.0.1:4318/v1/traces"); } + public String getContentType() { return "application/x-protobuf"; } + public io.opentelemetry.sdk.common.export.Compressor getCompressor() { return null; } + public java.time.Duration getTimeout() { return java.time.Duration.ofSeconds(3); } + public java.time.Duration getConnectTimeout() { return getTimeout(); } + public java.util.function.Supplier>> getHeadersSupplier() { return Map::of; } + public io.opentelemetry.sdk.common.export.ProxyOptions getProxyOptions() { return null; } + public io.opentelemetry.sdk.common.export.RetryPolicy getRetryPolicy() { return null; } + public javax.net.ssl.SSLContext getSslContext() { return null; } + public javax.net.ssl.X509TrustManager getTrustManager() { return null; } + public java.util.concurrent.ExecutorService getExecutorService() { return null; } + public long getMaxResponseBodySize() { return 8 * 1024 * 1024; } + }; + var bound = OwnedHttpComponents.bounded(source); + assertEquals(OtlpResponseAccounting.MAX_RESPONSE_BYTES, bound.getMaxResponseBodySize()); + assertEquals(8 * 1024 * 1024, source.getMaxResponseBodySize()); + assertEquals(source.getEndpoint(), bound.getEndpoint()); assertEquals(source.getTimeout(), bound.getTimeout()); + assertNull(bound.getRetryPolicy()); + } + + @Test public void actualFactoryPartialResponsesDoNotRetryOrTurnIntoWholeBatchFailure() throws Exception { + HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + AtomicInteger requests = new AtomicInteger(); + server.createContext("/", exchange -> { + requests.incrementAndGet(); exchange.getRequestBody().readAllBytes(); + var signal = exchange.getRequestURI().getPath().endsWith("traces") ? ExportLossAccounting.Signal.TRACES + : exchange.getRequestURI().getPath().endsWith("logs") ? ExportLossAccounting.Signal.LOGS : ExportLossAccounting.Signal.METRICS; + byte[] body = reply(signal, 1); exchange.getResponseHeaders().add("Content-Type", "application/x-protobuf"); + exchange.sendResponseHeaders(200, body.length); exchange.getResponseBody().write(body); exchange.close(); + }); + server.start(); StringWriter output = new StringWriter(); + var root = java.util.logging.Logger.getLogger(""); + Handler handler = new Handler() { + public synchronized void publish(LogRecord record) { + output.append(record.getMessage()).append('\n'); + if (record.getThrown() != null) record.getThrown().printStackTrace(new PrintWriter(output)); + } + public void flush() {} public void close() {} + }; + root.addHandler(handler); RuntimeTelemetry telemetry = null; var losses = new ExportLossAccounting(); + try { + telemetry = new StandardOpenTelemetryFactory().create("partial-response-test", + URI.create("http://127.0.0.1:" + server.getAddress().getPort()), losses); + telemetry.start(new RuntimeTelemetry.Operation("query", "School.list", Map.of())).success(); + telemetry.flush(); + assertEquals(3, requests.get()); + for (var signal : ExportLossAccounting.Signal.values()) { + assertEquals(1, losses.responses().rejected(signal)); + assertEquals(1, losses.responses().responses(signal, OtlpResponseAccounting.State.PARTIAL_SUCCESS)); + assertEquals(0, losses.lost(signal)); + } + } finally { + if (telemetry != null) telemetry.shutdown(); root.removeHandler(handler); server.stop(0); + } + assertFalse(output.toString(), output.toString().contains(SECRET)); + assertTrue(output.toString().contains("state=partial_success")); + } + + private static HttpResponse http(int status, byte[] body) { + return new HttpResponse() { + public int getStatusCode() { return status; } + public String getStatusMessage() { throw new AssertionError("remote reason must remain unread"); } + public byte[] getResponseBody() { return body; } + }; + } + + private static byte[] reply(ExportLossAccounting.Signal signal, long count) { + return switch (signal) { + case TRACES -> ExportTraceServiceResponse.newBuilder().setPartialSuccess( + ExportTracePartialSuccess.newBuilder().setRejectedSpans(count).setErrorMessage(SECRET)).build().toByteArray(); + case LOGS -> ExportLogsServiceResponse.newBuilder().setPartialSuccess( + ExportLogsPartialSuccess.newBuilder().setRejectedLogRecords(count).setErrorMessage(SECRET)).build().toByteArray(); + case METRICS -> ExportMetricsServiceResponse.newBuilder().setPartialSuccess( + ExportMetricsPartialSuccess.newBuilder().setRejectedDataPoints(count).setErrorMessage(SECRET)).build().toByteArray(); + }; + } +} diff --git a/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/OwnedExporterFailureTest.java b/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/OwnedExporterFailureTest.java new file mode 100644 index 00000000..6f5d80a9 --- /dev/null +++ b/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/OwnedExporterFailureTest.java @@ -0,0 +1,134 @@ +package io.teaql.opentelemetry; + +import com.sun.net.httpserver.HttpServer; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.HttpResponse; +import io.opentelemetry.sdk.common.export.HttpSender; +import io.opentelemetry.sdk.common.export.MessageWriter; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import io.teaql.runtime.RuntimeTelemetry; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.InetSocketAddress; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import org.junit.Test; +import static org.junit.Assert.*; + +public class OwnedExporterFailureTest { + private static final String SECRET = "EXPORTER_CANARY_8b013d1b_password"; + + @Test public void privateTransportNeverReadsResponseBodyOrReasonAndPreservesStatus() { + AtomicInteger callbacks = new AtomicInteger(); + var sender = OwnedHttpComponents.protect(sender((response, error) -> { + response.accept(new HttpResponse() { + public int getStatusCode() { return 503; } + public String getStatusMessage() { throw new AssertionError(SECRET); } + public byte[] getResponseBody() { throw new AssertionError(SECRET); } + }); + error.accept(new IllegalStateException(SECRET)); // Duplicate terminal callback is ignored. + })); + sender.send(null, response -> { + callbacks.incrementAndGet(); assertEquals(503, response.getStatusCode()); + assertEquals(0, response.getResponseBody().length); + assertFalse(response.getStatusMessage().contains(SECRET)); + }, error -> fail("already completed")); + assertEquals(1, callbacks.get()); + } + + @Test public void privateTransportDropsExceptionMessageCauseSuppressedAndStack() { + var original = new IllegalStateException(SECRET, new IllegalArgumentException(SECRET)); + original.addSuppressed(new RuntimeException(SECRET)); + AtomicReference failure = new AtomicReference<>(); + OwnedHttpComponents.protect(sender((response, error) -> error.accept(original))) + .send(null, response -> fail("unexpected response"), failure::set); + assertNotNull(failure.get()); assertNull(failure.get().getCause()); + assertEquals(0, failure.get().getSuppressed().length); assertEquals(0, failure.get().getStackTrace().length); + assertFalse(failure.get().getMessage().contains(SECRET)); + } + + @Test public void failedBatchesCountExactlyOnceAndSuccessfulExportDoesNotIncreaseLoss() { + var losses = new ExportLossAccounting(); + CompletableResultCode completion = new CompletableResultCode(); + SpanExporter exporter = losses.spans(new SpanExporter() { + public CompletableResultCode export(Collection values) { return completion; } + public CompletableResultCode flush() { return CompletableResultCode.ofSuccess(); } + public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } + }); + // Null fixture entries are not marshaled; only batch cardinality matters here. + exporter.export(java.util.Arrays.asList(null, null, null)); + completion.fail(); completion.fail(); + assertEquals(3, losses.lost(ExportLossAccounting.Signal.TRACES)); + losses.spans(SpanExporter.noop()).export(List.of()); + assertEquals(3, losses.lost(ExportLossAccounting.Signal.TRACES)); + var reader = InMemoryMetricReader.create(); + try (var metrics = SdkMeterProvider.builder().registerMetricReader(reader).build()) { + losses.bind(metrics.get("io.teaql.runtime")); + var counter = reader.collectAllMetrics().stream().filter(value -> value.getName().equals("teaql.telemetry.export.lost")).findFirst().orElseThrow(); + assertEquals(3, counter.getLongSumData().getPoints().stream().mapToLong(point -> point.getValue()).sum()); + assertFalse(counter.toString().contains(SECRET)); + } + } + + @Test public void realThreeSignalFailuresAreMaskedCountedAndDoNotReplaceCustomerSdk() throws Exception { + HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + AtomicInteger requests = new AtomicInteger(); + server.createContext("/", exchange -> { + requests.incrementAndGet(); exchange.getRequestBody().readAllBytes(); + byte[] body = SECRET.getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(503, body.length); + exchange.getResponseBody().write(body); exchange.close(); + }); + server.start(); + StringWriter output = new StringWriter(); + Handler handler = new Handler() { + public synchronized void publish(LogRecord record) { + output.append(record.getMessage()).append('\n'); + if (record.getThrown() != null) record.getThrown().printStackTrace(new PrintWriter(output)); + } + public void flush() {} public void close() {} + }; + var root = java.util.logging.Logger.getLogger(""); + var handlers = root.getHandlers(); var level = root.getLevel(); var global = GlobalOpenTelemetry.get(); + root.addHandler(handler); // Observe only; never suppress/change customer log configuration. + var losses = new ExportLossAccounting(); + RuntimeTelemetry telemetry = null; + try { + telemetry = new StandardOpenTelemetryFactory().create("safe-failure-test", + URI.create("http://127.0.0.1:" + server.getAddress().getPort()), losses); + var operation = telemetry.start(new RuntimeTelemetry.Operation("query", "School.list", Map.of())); + operation.success(Map.of("teaql.result.cardinality", 1)); + telemetry.flush(); + assertTrue(losses.lost(ExportLossAccounting.Signal.TRACES) > 0); + assertTrue(losses.lost(ExportLossAccounting.Signal.LOGS) > 0); + assertTrue(losses.lost(ExportLossAccounting.Signal.METRICS) > 0); + assertEquals("one request per signal, no implicit exporter retries", 3, requests.get()); + assertSame(global, GlobalOpenTelemetry.get()); + } finally { + if (telemetry != null) { telemetry.shutdown(); telemetry.shutdown(); } + root.removeHandler(handler); server.stop(0); + } + assertFalse(output.toString(), output.toString().contains(SECRET)); + assertTrue(output.toString().contains("remote response masked")); + assertTrue(output.toString().contains("export entries lost")); + assertArrayEquals(handlers, root.getHandlers()); assertEquals(level, root.getLevel()); + } + + private static HttpSender sender(java.util.function.BiConsumer, Consumer> complete) { + return new HttpSender() { + public void send(MessageWriter message, Consumer response, Consumer error) { complete.accept(response, error); } + public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } + }; + } +} diff --git a/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/SqlDiagnosticCorrelationTest.java b/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/SqlDiagnosticCorrelationTest.java new file mode 100644 index 00000000..d9c87153 --- /dev/null +++ b/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/SqlDiagnosticCorrelationTest.java @@ -0,0 +1,104 @@ +package io.teaql.opentelemetry; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.sdk.logs.SdkLoggerProvider; +import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.teaql.core.*; +import io.teaql.core.meta.SimpleEntityMetaFactory; +import io.teaql.runtime.*; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import static org.junit.Assert.*; + +public class SqlDiagnosticCorrelationTest { + @Test public void lateSqlLogUsesCarriedIdentityAndMaskProjectionNotActiveSpanOrDebugMode() { + var spans = InMemorySpanExporter.create(); + var logs = InMemoryLogRecordExporter.create(); + try (var traces = SdkTracerProvider.builder().addSpanProcessor(SimpleSpanProcessor.create(spans)).build(); + var meters = SdkMeterProvider.builder().build(); + var logProvider = SdkLoggerProvider.builder().addLogRecordProcessor(SimpleLogRecordProcessor.create(logs)).build()) { + var telemetry = new OpenTelemetryRuntimeTelemetry(traces.get("test"), meters.get("test"), logProvider.get("test")); + var context = new DefaultUserContext(TeaQLRuntime.builder().metadata(new SimpleEntityMetaFactory()) + .telemetry(telemetry).queryExecutionLogging(false).mutationExecutionLogging(false).build()); + var root = telemetry.start(new RuntimeTelemetry.Operation("query", "School.list", Map.of())); + var first = telemetry.start(new RuntimeTelemetry.Operation("provider", "sqlite.statement", Map.of())); + var firstIdentity = first.correlation(); + first.success(); + var second = telemetry.start(new RuntimeTelemetry.Operation("provider", "sqlite.statement", Map.of())); + var secondIdentity = second.correlation(); + second.success(); + root.success(); + var unrelated = telemetry.start(new RuntimeTelemetry.Operation("query", "Other.list", Map.of())); + String unrelatedId = Span.current().getSpanContext().getSpanId(); + context.recordExecutionMetadata(metadata(secondIdentity)); + var plaintextDebug = LogPrivacy.sql(metadata(firstIdentity), true); + assertTrue(plaintextDebug.getDebugQuery().contains("PRIVATE-SQL-CANARY")); + plaintextDebug.setSqlExecutionCorrelation(firstIdentity); + context.recordExecutionMetadata(plaintextDebug); + assertEquals(unrelatedId, Span.current().getSpanContext().getSpanId()); + unrelated.success(); + + var sqlLogs = logs.getFinishedLogRecordItems().stream() + .filter(log -> log.getBodyValue().asString().equals("TeaQL SQL execution")).toList(); + assertEquals(2, sqlLogs.size()); + assertEquals(secondIdentity.spanId(), sqlLogs.get(0).getSpanContext().getSpanId()); + assertEquals(firstIdentity.spanId(), sqlLogs.get(1).getSpanContext().getSpanId()); + assertEquals(firstIdentity.traceId(), sqlLogs.get(1).getSpanContext().getTraceId()); + assertNotEquals(unrelatedId, sqlLogs.get(1).getSpanContext().getSpanId()); + for (var log : sqlLogs) { + assertFalse(log.getAttributes().toString().contains("PRIVATE-SQL-CANARY")); + assertFalse(log.getAttributes().toString().contains("9988776655")); + assertFalse(log.getAttributes().toString().contains("DEBUG PLAINTEXT")); + assertTrue(log.getAttributes().get(AttributeKey.stringKey("teaql.sql.debug")).contains("/* masked */")); + assertTrue(spans.getFinishedSpanItems().stream().anyMatch(span -> span.getSpanId().equals(log.getSpanContext().getSpanId()))); + } + assertEquals("show school details", sqlLogs.get(0).getAttributes().get(AttributeKey.stringKey("teaql.intent.purpose"))); + // Context canonicalization changes a debug projection's fingerprint. The existing + // privacy policy conservatively redacts orphaned intent instead of trusting it. + assertEquals("[REDACTED]", sqlLogs.get(1).getAttributes().get(AttributeKey.stringKey("teaql.intent.purpose"))); + var query = spans.getFinishedSpanItems().stream().filter(span -> "School.list".equals( + span.getAttributes().get(AttributeKey.stringKey("teaql.operation.name")))).findFirst().orElseThrow(); + assertEquals(2, spans.getFinishedSpanItems().stream().filter(span -> span.getParentSpanId().equals(query.getSpanId())).count()); + } + } + + private static ExecutionMetadata metadata(SqlExecutionCorrelation identity) { + var metadata = new ExecutionMetadata(); + metadata.setBackend("sqlite"); metadata.setOperation(DataServiceOperation.QUERY); + metadata.setParameterizedQuery("SELECT id FROM school WHERE name = ?"); + metadata.setParameters(List.of("PRIVATE-SQL-CANARY")); + metadata.setParameterLogPolicies(List.of(SqlParameterLogPolicy.MASKED)); + metadata.setGeneratedSql(true); metadata.setExecutionOutcome("success"); metadata.setResultCount(1); + metadata.setPurpose("show school details"); metadata.setComment("load PRIVATE-SQL-CANARY"); + metadata.setTraceChain(List.of(new TraceNode(TraceKind.COMMENT, "School", "load PRIVATE-SQL-CANARY"), + new TraceNode(TraceKind.PURPOSE, "School", "show school details"), + new TraceNode(TraceKind.ENTITY, "School", 9988776655L, ""))); + metadata.setRequestTraceChain(List.of(new TraceNode(TraceKind.COMMENT, "School", "load PRIVATE-SQL-CANARY"))); + metadata.setSqlExecutionCorrelation(identity); + return metadata; + } + + @Test public void detachedSpanLogsKeepIdentityAndDoNotLeakThreadActivation() { + var logs = InMemoryLogRecordExporter.create(); + try (var traces = SdkTracerProvider.builder().build(); var meters = SdkMeterProvider.builder().build(); + var logProvider = SdkLoggerProvider.builder().addLogRecordProcessor(SimpleLogRecordProcessor.create(logs)).build()) { + var telemetry = new OpenTelemetryRuntimeTelemetry(traces.get("test"), meters.get("test"), logProvider.get("test")); + var span = telemetry.start(new RuntimeTelemetry.Operation("provider", "cursor", Map.of())); + var identity = span.correlation(); + span.detach(); + assertFalse(Span.current().getSpanContext().isValid()); + try (var active = span.activate()) { assertEquals(identity.spanId(), Span.current().getSpanContext().getSpanId()); } + assertFalse(Span.current().getSpanContext().isValid()); + span.success(); + assertEquals(identity.spanId(), logs.getFinishedLogRecordItems().get(0).getSpanContext().getSpanId()); + assertFalse(Span.current().getSpanContext().isValid()); + } + } +} diff --git a/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/StandardOpenTelemetryFactoryTest.java b/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/StandardOpenTelemetryFactoryTest.java new file mode 100644 index 00000000..bd33790c --- /dev/null +++ b/teaql-opentelemetry/src/test/java/io/teaql/opentelemetry/StandardOpenTelemetryFactoryTest.java @@ -0,0 +1,32 @@ +package io.teaql.opentelemetry; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.teaql.runtime.RuntimeTelemetry; +import java.net.URI; +import java.net.ServerSocket; +import java.util.Map; +import org.junit.Test; +import static org.junit.Assert.*; + +public class StandardOpenTelemetryFactoryTest { + @Test public void ownedFactoryRecordsAllAndDoesNotReplaceGlobalSdk() throws Exception { + var global = GlobalOpenTelemetry.get(); + int closedPort; + try (var socket = new ServerSocket(0)) { closedPort = socket.getLocalPort(); } + RuntimeTelemetry telemetry = new StandardOpenTelemetryFactory().create("standard-factory-test", + URI.create("http://127.0.0.1:" + closedPort)); + long start = System.nanoTime(); + try { + RuntimeTelemetry.Scope scope = telemetry.start(new RuntimeTelemetry.Operation("query", "School.list", + Map.of("teaql.entity.type", "School"))); + assertTrue(Span.current().getSpanContext().isValid()); + assertTrue(Span.current().getSpanContext().isSampled()); + scope.success(Map.of("teaql.result.cardinality", 1)); + assertSame(global, GlobalOpenTelemetry.get()); + telemetry.flush(); // Unavailable destination must not become a business exception. + } finally { telemetry.shutdown(); telemetry.shutdown(); } + assertSame(global, GlobalOpenTelemetry.get()); + assertTrue("Owned lifecycle must be bounded", System.nanoTime() - start < 20_000_000_000L); + } +} diff --git a/teaql-oracle/pom.xml b/teaql-oracle/pom.xml index bc01a766..4ca11649 100644 --- a/teaql-oracle/pom.xml +++ b/teaql-oracle/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-oracle teaql-oracle diff --git a/teaql-postgres/pom.xml b/teaql-postgres/pom.xml index 144c3cf8..acb8f0fd 100644 --- a/teaql-postgres/pom.xml +++ b/teaql-postgres/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-postgres teaql-postgres diff --git a/teaql-provider-jdbc/pom.xml b/teaql-provider-jdbc/pom.xml index 1de23b21..ce07e33d 100644 --- a/teaql-provider-jdbc/pom.xml +++ b/teaql-provider-jdbc/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-provider-spring-jdbc/pom.xml b/teaql-provider-spring-jdbc/pom.xml index ff392d65..3dc9c064 100644 --- a/teaql-provider-spring-jdbc/pom.xml +++ b/teaql-provider-spring-jdbc/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-query-json/pom.xml b/teaql-query-json/pom.xml index 94c61415..ed154fd5 100644 --- a/teaql-query-json/pom.xml +++ b/teaql-query-json/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-query-json diff --git a/teaql-query-json/src/test/java/io/teaql/query/json/RequestTreeEvidenceJsonTest.java b/teaql-query-json/src/test/java/io/teaql/query/json/RequestTreeEvidenceJsonTest.java new file mode 100644 index 00000000..c56ee57f --- /dev/null +++ b/teaql-query-json/src/test/java/io/teaql/query/json/RequestTreeEvidenceJsonTest.java @@ -0,0 +1,23 @@ +package io.teaql.query.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.teaql.core.*; +import java.util.List; +import org.junit.Test; +import static org.junit.Assert.*; + +/** Evidence JSON uses business traceChain and explicitly named physical tracePath. */ +public class RequestTreeEvidenceJsonTest { + @Test public void legacyPhysicalGetterCannotReplaceBusinessTraceChainOnTheWire() throws Exception { + ExecutionMetadata metadata = new ExecutionMetadata(); + metadata.setTraceChain(List.of(new TraceNode(TraceKind.PROVIDER, "sqlite", ""))); + metadata.setRequestTraceChain(List.of(new TraceNode(TraceKind.PURPOSE, "School", "render schools"), + new TraceNode(TraceKind.COMMENT, "School", "load schools"))); + var mapper = new ObjectMapper(); + var tree = mapper.valueToTree(metadata); + assertEquals("PURPOSE", tree.path("traceChain").get(0).path("kind").asText()); + assertEquals("PROVIDER", tree.path("tracePath").get(0).path("kind").asText()); + assertFalse(tree.has("requestTraceChain")); + assertFalse(tree.has("requestScope")); + } +} diff --git a/teaql-redis/pom.xml b/teaql-redis/pom.xml index bf724fe7..6ec5afea 100644 --- a/teaql-redis/pom.xml +++ b/teaql-redis/pom.xml @@ -7,7 +7,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-runtime-log/pom.xml b/teaql-runtime-log/pom.xml index 1cc4b2a6..c93c4304 100644 --- a/teaql-runtime-log/pom.xml +++ b/teaql-runtime-log/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-runtime-log diff --git a/teaql-runtime/pom.xml b/teaql-runtime/pom.xml index 2d212f7c..2acd8ff7 100644 --- a/teaql-runtime/pom.xml +++ b/teaql-runtime/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-runtime @@ -24,6 +24,10 @@ + + com.fasterxml.jackson.core + jackson-databind + junit junit diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/DefaultQueryRequest.java b/teaql-runtime/src/main/java/io/teaql/runtime/DefaultQueryRequest.java index ef238ede..d58ba8b8 100644 --- a/teaql-runtime/src/main/java/io/teaql/runtime/DefaultQueryRequest.java +++ b/teaql-runtime/src/main/java/io/teaql/runtime/DefaultQueryRequest.java @@ -8,6 +8,7 @@ public final class DefaultQueryRequest implements QueryRequest { private final SearchRequest searchRequest; private final QueryIntent intent; + private final io.teaql.core.QueryRequestScope scope; public DefaultQueryRequest(SearchRequest searchRequest) { this(searchRequest, searchRequest.inheritedQueryIntent() == null @@ -18,7 +19,30 @@ public DefaultQueryRequest(SearchRequest searchRequest) { /** Framework-derived requests inherit an already validated root intent, never ambient trace. */ public DefaultQueryRequest(SearchRequest searchRequest, QueryIntent intent) { this.intent = Objects.requireNonNull(intent, "intent"); - this.searchRequest = Objects.requireNonNull(searchRequest, "searchRequest"); + Objects.requireNonNull(searchRequest, "searchRequest"); + this.scope = searchRequest.requestScope() == null + ? io.teaql.core.QueryRequestScope.root(searchRequest.getTypeName(), intent) : searchRequest.requestScope(); + this.searchRequest = new io.teaql.core.QueryRequestSnapshot(searchRequest, scope); + } + + @Override public io.teaql.core.QueryRequestScope requestScope() { return scope; } + + public DefaultQueryRequest withQuery(SearchRequest query) { + if (query.requestScope() != null && query.requestScope().rootRequest() != scope.rootRequest()) + throw new IllegalArgumentException("QUERY_SCOPE_OWNER_MISMATCH: replacement belongs to another query invocation"); + return new DefaultQueryRequest(new io.teaql.core.QueryRequestSnapshot(query, scope), intent); + } + + public DefaultQueryRequest deriveOperation(SearchRequest query, io.teaql.core.QueryOperation operation) { + return new DefaultQueryRequest(new io.teaql.core.QueryRequestSnapshot(query, + scope.deriveOperation(query.getTypeName(), operation)), intent); + } + + public DefaultQueryRequest derive(SearchRequest child, io.teaql.core.QueryOperation operation) { + var parent = scope.deriveOperation(searchRequest.getTypeName(), operation); + String comment = child.requestScope() == null ? child.comment() : child.requestScope().comment(); + return new DefaultQueryRequest(new io.teaql.core.QueryRequestSnapshot(child, + parent.deriveChild(child.getTypeName(), comment)), intent); } @Override public QueryIntent intent() { return intent; } diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/DefaultTextRuntimeLogSink.java b/teaql-runtime/src/main/java/io/teaql/runtime/DefaultTextRuntimeLogSink.java index de270397..01c4188e 100644 --- a/teaql-runtime/src/main/java/io/teaql/runtime/DefaultTextRuntimeLogSink.java +++ b/teaql-runtime/src/main/java/io/teaql/runtime/DefaultTextRuntimeLogSink.java @@ -28,14 +28,14 @@ public boolean requiresSensitiveSqlData() { public void writeExecutionLog(UserContext context, ExecutionMetadata metadata) { metadata = LogPrivacy.sql(metadata, false); output.printf( - "[TeaQL SQL][%s][%dus] %s outcome=%s batchOutcome=%s comment=%s purpose=%s auditReason=%s tracePath=%s mutationLineage=%s%n" + "[TeaQL SQL][%s][%dus] %s outcome=%s batchOutcome=%s comment=%s purpose=%s auditReason=%s traceChain=%s tracePath=%s mutationLineage=%s%n" + "SQL: %s%n", metadata.getOperation() == null ? "unknown" : metadata.getOperation().name().toLowerCase(), metadata.getElapsedUs(), resultSummary(metadata), metadata.getExecutionOutcome() == null ? "unknown" : metadata.getExecutionOutcome(), metadata.getBatchOutcome() == null ? "not_applicable" : metadata.getBatchOutcome(), nullToEmpty(metadata.getComment()), nullToEmpty(metadata.getPurpose()), - nullToEmpty(metadata.getAuditReason()), formatTrace(metadata.getTraceChain()), + nullToEmpty(metadata.getAuditReason()), formatTrace(metadata.getRequestTraceChain()), formatTrace(metadata.getTracePath()), formatTrace(metadata.getMutationLineage()), nullToEmpty(metadata.getDebugQuery())); } diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/DefaultUserContext.java b/teaql-runtime/src/main/java/io/teaql/runtime/DefaultUserContext.java index 4c7413a0..eb96815d 100644 --- a/teaql-runtime/src/main/java/io/teaql/runtime/DefaultUserContext.java +++ b/teaql-runtime/src/main/java/io/teaql/runtime/DefaultUserContext.java @@ -75,12 +75,6 @@ public SmartList executeForList(ExecutableRequest reque return runtime.executeForList(this, request.request()); } - @Override - public SmartList executeForPage( - ExecutableRequest request, int offset, int limit) { - return runtime.executeForPage(this, request.request(), offset, limit); - } - @Override public Stream executeForStream(ExecutableRequest request) { return runtime.executeForStream(this, request.request()); @@ -312,6 +306,11 @@ public void recordExecutionMetadata(io.teaql.core.ExecutionMetadata metadata) { } } + @Override + public io.teaql.core.SqlStatementObservation startSqlStatement(String backend, io.teaql.core.DataServiceOperation operation) { + return runtime == null ? io.teaql.core.SqlStatementObservation.NOOP : runtime.startSqlStatement(backend, operation); + } + private static String sqlOperation(io.teaql.core.ExecutionMetadata metadata) { if (metadata.getStatementOperation() != null) return metadata.getStatementOperation(); String sql = metadata.getParameterizedQuery(); diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/LogPrivacy.java b/teaql-runtime/src/main/java/io/teaql/runtime/LogPrivacy.java index 62c36cef..e78cbeb0 100644 --- a/teaql-runtime/src/main/java/io/teaql/runtime/LogPrivacy.java +++ b/teaql-runtime/src/main/java/io/teaql/runtime/LogPrivacy.java @@ -116,6 +116,7 @@ public static ExecutionMetadata sql(ExecutionMetadata source, boolean allow) { safe.setBackendRequestId(scrub(source.getBackendRequestId(), secrets)); safe.setComment(scrub(source.getComment(), secrets)); safe.setPurpose(scrub(source.getPurpose(), secrets)); safe.setAuditReason(scrub(source.getAuditReason(), secrets)); safe.setTraceChain(trace(source.getTraceChain(), secrets)); + safe.setRequestTraceChain(trace(source.getRequestTraceChain(), secrets)); safe.setMutationLineage(trace(source.getMutationLineage(), secrets)); safe.setStatementOperation(source.getStatementOperation()); if (orphanedDebug) { @@ -128,6 +129,8 @@ public static ExecutionMetadata sql(ExecutionMetadata source, boolean allow) { .map(node -> new TraceNode(node.getKind(), hideIntent(node.getName()), node.getEntityId(), hideIntent(node.getComment()))).toList()); safe.setMutationLineage(source.getMutationLineage().stream() .map(node -> new TraceNode(node.getKind(), hideIntent(node.getName()), node.getEntityId(), hideIntent(node.getComment()))).toList()); + safe.setRequestTraceChain(source.getRequestTraceChain().stream() + .map(node -> new TraceNode(node.getKind(), hideIntent(node.getName()), node.getEntityId(), hideIntent(node.getComment()))).toList()); } String sql = source.getParameterizedQuery(); safe.setParameterizedQuery(sql); diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/RuntimeQueryStream.java b/teaql-runtime/src/main/java/io/teaql/runtime/RuntimeQueryStream.java new file mode 100644 index 00000000..cda291a3 --- /dev/null +++ b/teaql-runtime/src/main/java/io/teaql/runtime/RuntimeQueryStream.java @@ -0,0 +1,68 @@ +package io.teaql.runtime; + +import java.util.Map; +import java.util.Spliterator; +import java.util.concurrent.CancellationException; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** Query ownership follows cursor consumption; never leaves a scope active in the caller. */ +final class RuntimeQueryStream implements Spliterator { + private final Stream source; + private final Spliterator rows; + private final RuntimeTelemetry.Scope scope; + private boolean done; + private long delivered; + + private RuntimeQueryStream(Stream source, RuntimeTelemetry.Scope scope) { + this.source = source; this.scope = scope; this.rows = source.spliterator(); + } + + static Stream wrap(Stream source, RuntimeTelemetry.Scope scope) { + try { + var cursor = new RuntimeQueryStream<>(source, scope); + var result = StreamSupport.stream(cursor, false).onClose(() -> cursor.finish(true, null)); + scope.detach(); + return result; + } catch (RuntimeException | Error failure) { + try { source.close(); } catch (RuntimeException | Error closeFailure) { + if (failure != closeFailure) failure.addSuppressed(closeFailure); + } + scope.failure(failure); throw failure; + } + } + + @Override public boolean tryAdvance(Consumer action) { + if (done) return false; + try (var activation = scope.activate()) { + boolean more = rows.tryAdvance(value -> { delivered++; action.accept(value); }); + if (!more) finish(false, null); + return more; + } catch (RuntimeException | Error failure) { + finish(false, failure); throw failure; + } + } + + private void finish(boolean cancelled, Throwable original) { + if (done) return; + done = true; + Throwable failure = original; + try (var activation = scope.activate()) { source.close(); } + catch (RuntimeException | Error closeFailure) { + if (failure == null) failure = closeFailure; + else if (failure != closeFailure) failure.addSuppressed(closeFailure); + } + if (failure != null) scope.failure(failure); + else if (cancelled) scope.failure(new CancellationException()); + else scope.success(Map.of("teaql.result.cardinality", delivered)); + if (original == null) { + if (failure instanceof RuntimeException error) throw error; + if (failure instanceof Error error) throw error; + } + } + + @Override public Spliterator trySplit() { return null; } + @Override public long estimateSize() { return Long.MAX_VALUE; } + @Override public int characteristics() { return ORDERED; } +} diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/RuntimeTelemetry.java b/teaql-runtime/src/main/java/io/teaql/runtime/RuntimeTelemetry.java index ba5b5309..752ed72a 100644 --- a/teaql-runtime/src/main/java/io/teaql/runtime/RuntimeTelemetry.java +++ b/teaql-runtime/src/main/java/io/teaql/runtime/RuntimeTelemetry.java @@ -20,6 +20,15 @@ public interface RuntimeTelemetry { Scope start(Operation operation); + /** Whether statement diagnostics should be collected independently of ordinary logging. */ + default boolean recordsSqlDiagnostics() { return false; } + + default void recordDiagnostic(Diagnostic event) {} + + record Diagnostic(io.teaql.core.SqlExecutionCorrelation correlation, Map attributes) { + public Diagnostic { attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes)); } + } + default void inject(Map carrier) {} default PropagationScope extractAndActivate(Map carrier) { @@ -37,6 +46,7 @@ static String errorCategory(Throwable error) { static String errorCategory(String errorType) { String type = errorType == null ? "unknown" : errorType.toLowerCase(java.util.Locale.ROOT); + if (containsAny(type, "cancel")) return "cancelled"; if (containsAny(type, "timeout", "deadline")) return "timeout"; if (containsAny(type, "authentication", "authorization", "unauthorized", "forbidden", "permission")) return "authorization"; if (containsAny(type, "validation", "invalidargument", "valueerror", "parse", "format")) return "validation"; @@ -76,6 +86,10 @@ interface Scope { void failure(Throwable error); + default io.teaql.core.SqlExecutionCorrelation correlation() { return null; } + default void detach() {} + default PropagationScope activate() { return NoopPropagationScope.INSTANCE; } + default void success() { success(Collections.emptyMap()); } @@ -162,6 +176,19 @@ private FailOpenScope(Scope delegate) { this.delegate = delegate; } + @Override public io.teaql.core.SqlExecutionCorrelation correlation() { + try { return delegate.correlation(); } catch (Throwable ignored) { return null; } + } + @Override public void detach() { + try { delegate.detach(); } catch (Throwable ignored) {} + } + @Override public PropagationScope activate() { + try { + var scope = delegate.activate(); + return scope == null ? NoopPropagationScope.INSTANCE : new FailOpenPropagationScope(scope); + } catch (Throwable ignored) { return NoopPropagationScope.INSTANCE; } + } + @Override public synchronized void success(Map attributes) { if (ended) return; diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/SensitiveDiagnosticTextRuntimeLogSink.java b/teaql-runtime/src/main/java/io/teaql/runtime/SensitiveDiagnosticTextRuntimeLogSink.java index cb8155bd..9cf669a3 100644 --- a/teaql-runtime/src/main/java/io/teaql/runtime/SensitiveDiagnosticTextRuntimeLogSink.java +++ b/teaql-runtime/src/main/java/io/teaql/runtime/SensitiveDiagnosticTextRuntimeLogSink.java @@ -29,14 +29,14 @@ public boolean requiresSensitiveSqlData() { public void writeExecutionLog(UserContext context, ExecutionMetadata metadata) { metadata = LogPrivacy.sql(metadata, LogPrivacy.plaintextEnabled()); output.printf( - "[TeaQL SQL][%s][%dus] %s outcome=%s batchOutcome=%s comment=%s purpose=%s auditReason=%s tracePath=%s mutationLineage=%s%n" + "[TeaQL SQL][%s][%dus] %s outcome=%s batchOutcome=%s comment=%s purpose=%s auditReason=%s traceChain=%s tracePath=%s mutationLineage=%s%n" + "SQL: %s%n", metadata.getOperation() == null ? "unknown" : metadata.getOperation().name().toLowerCase(), metadata.getElapsedUs(), resultSummary(metadata), metadata.getExecutionOutcome() == null ? "unknown" : metadata.getExecutionOutcome(), metadata.getBatchOutcome() == null ? "not_applicable" : metadata.getBatchOutcome(), nullToEmpty(metadata.getComment()), nullToEmpty(metadata.getPurpose()), - nullToEmpty(metadata.getAuditReason()), formatTrace(metadata.getTraceChain()), + nullToEmpty(metadata.getAuditReason()), formatTrace(metadata.getRequestTraceChain()), formatTrace(metadata.getTracePath()), formatTrace(metadata.getMutationLineage()), nullToEmpty(metadata.getDebugQuery())); } diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/SqlDiagnosticEvent.java b/teaql-runtime/src/main/java/io/teaql/runtime/SqlDiagnosticEvent.java new file mode 100644 index 00000000..de22667d --- /dev/null +++ b/teaql-runtime/src/main/java/io/teaql/runtime/SqlDiagnosticEvent.java @@ -0,0 +1,47 @@ +package io.teaql.runtime; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.teaql.core.ExecutionMetadata; +import io.teaql.core.TraceNode; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** Explicit allowlist applied after the existing mask projection. No metadata/object spreading. */ +final class SqlDiagnosticEvent { + private static final ObjectMapper JSON = new ObjectMapper(); + static RuntimeTelemetry.Diagnostic project(ExecutionMetadata raw) throws JsonProcessingException { + ExecutionMetadata safe = LogPrivacy.sql(raw, false); + Map attributes = new LinkedHashMap<>(); + put(attributes, "teaql.sql.debug", safe.getDebugQuery()); + // The export channel always uses the mask policy; explicitly PLAIN parameters + // remain allowed by that policy. Unsafe inline SQL is omitted, never exported raw. + put(attributes, "teaql.sql.log_mode", "OMITTED".equals(safe.getLogMode()) ? "omitted" : "masked"); + put(attributes, "teaql.intent.comment", safe.getComment() != null ? safe.getComment() : safe.getAuditReason()); + put(attributes, "teaql.intent.purpose", safe.getPurpose()); + put(attributes, "teaql.intent.audit_reason", safe.getAuditReason()); + put(attributes, "teaql.sql.operation", safe.getStatementOperation()); + put(attributes, "teaql.sql.outcome", safe.getExecutionOutcome()); + put(attributes, "teaql.sql.batch_outcome", safe.getBatchOutcome()); + put(attributes, "teaql.sql.elapsed_us", safe.getElapsedUs()); + put(attributes, "teaql.sql.result_count", safe.getResultCount()); + put(attributes, "teaql.sql.affected_rows", safe.getAffectedRows()); + put(attributes, "teaql.trace_chain", JSON.writeValueAsString(path(safe.getRequestTraceChain()))); + put(attributes, "teaql.trace_path", JSON.writeValueAsString(path(safe.getTracePath()))); + put(attributes, "teaql.mutation_lineage", JSON.writeValueAsString(path(safe.getMutationLineage()))); + return new RuntimeTelemetry.Diagnostic(raw.getSqlExecutionCorrelation(), attributes); + } + private static void put(Map values, String key, Object value) { if (value != null) values.put(key, value); } + private static List> path(List nodes) { + if (nodes == null) return List.of(); + return nodes.stream().map(node -> { + Map fields = new LinkedHashMap<>(); + put(fields, "kind", node.getKind() == null ? null : node.getKind().name().toLowerCase(java.util.Locale.ROOT)); + put(fields, "name", node.getName()); + put(fields, "detail", node.getComment()); + // TraceNode.entityId is intentionally never exported. + return fields; + }).toList(); + } +} diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/SqlLogProjectionCache.java b/teaql-runtime/src/main/java/io/teaql/runtime/SqlLogProjectionCache.java index 10d60663..45b3b171 100644 --- a/teaql-runtime/src/main/java/io/teaql/runtime/SqlLogProjectionCache.java +++ b/teaql-runtime/src/main/java/io/teaql/runtime/SqlLogProjectionCache.java @@ -34,6 +34,10 @@ private static String fingerprint(ExecutionMetadata m) { add(digest, m.getBackendRequestId()); add(digest, m.getParameterizedQuery()); add(digest, m.getDebugQuery()); add(digest, m.getComment()); add(digest, m.getPurpose()); add(digest, m.getAuditReason()); add(digest, m.getStatementOperation()); + add(digest, m.getRequestTraceChain().size()); + for (var node : m.getRequestTraceChain()) { + add(digest, node.getKind()); add(digest, node.getName()); add(digest, node.getEntityId()); add(digest, node.getComment()); + } add(digest, m.getMutationLineage().size()); for (var node : m.getMutationLineage()) { add(digest, node.getKind()); add(digest, node.getName()); add(digest, node.getEntityId()); add(digest, node.getComment()); @@ -81,6 +85,7 @@ private static ExecutionMetadata copy(ExecutionMetadata m) { c.setBatchOutcome(m.getBatchOutcome()); c.setComment(m.getComment()); c.setPurpose(m.getPurpose()); c.setAuditReason(m.getAuditReason()); c.setTraceChain(m.getTraceChain() == null ? null : java.util.List.copyOf(m.getTraceChain())); + c.setRequestTraceChain(m.getRequestTraceChain()); c.setMutationLineage(m.getMutationLineage()); c.setStatementOperation(m.getStatementOperation()); return c; } diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/StandardTelemetryFactory.java b/teaql-runtime/src/main/java/io/teaql/runtime/StandardTelemetryFactory.java new file mode 100644 index 00000000..66b9b37d --- /dev/null +++ b/teaql-runtime/src/main/java/io/teaql/runtime/StandardTelemetryFactory.java @@ -0,0 +1,8 @@ +package io.teaql.runtime; + +import java.net.URI; + +/** Optional module SPI. Core never exposes SDK/exporter types. */ +public interface StandardTelemetryFactory { + RuntimeTelemetry create(String serviceName, URI endpoint); +} diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/TeaQLRuntime.java b/teaql-runtime/src/main/java/io/teaql/runtime/TeaQLRuntime.java index 2cdbd6e3..88d60e55 100644 --- a/teaql-runtime/src/main/java/io/teaql/runtime/TeaQLRuntime.java +++ b/teaql-runtime/src/main/java/io/teaql/runtime/TeaQLRuntime.java @@ -19,7 +19,7 @@ import java.util.*; import java.util.stream.Stream; -public class TeaQLRuntime { +public class TeaQLRuntime implements AutoCloseable { private final EntityMetaFactory metadata; private final DataServiceRegistry registry; private final QueryPolicy queryPolicy; @@ -30,6 +30,8 @@ public class TeaQLRuntime { private final boolean queryExecutionLoggingEnabled; private final boolean mutationExecutionLoggingEnabled; private final RuntimeTelemetry telemetry; + private final boolean ownsTelemetry; + private boolean telemetryClosed; private final SchemaExecutor schemaExecutor; private final BusinessIdService businessIdService; private final BusinessIdSchemaContributor businessIdSchemaContributor; @@ -52,7 +54,9 @@ private TeaQLRuntime(Builder builder) { this.logSink = builder.logSink; this.queryExecutionLoggingEnabled = builder.queryExecutionLoggingEnabled; this.mutationExecutionLoggingEnabled = builder.mutationExecutionLoggingEnabled; - this.telemetry = builder.telemetry != null ? builder.telemetry : RuntimeTelemetry.NOOP; + this.ownsTelemetry = builder.telemetry == null; + this.telemetry = builder.telemetry != null ? builder.telemetry + : TelemetryStartup.initialize(System.getenv(), TeaQLRuntime.class.getClassLoader()); this.schemaExecutor = builder.schemaExecutor; this.businessIdService = builder.businessIdService; this.businessIdSchemaContributor = builder.businessIdSchemaContributor; @@ -111,6 +115,14 @@ public RuntimeTelemetry getTelemetry() { return telemetry; } + @Override + public synchronized void close() { + if (!ownsTelemetry || telemetryClosed) return; + telemetryClosed = true; + try { telemetry.shutdown(); } + catch (Throwable ignored) { TelemetryStartup.notice("TeaQL telemetry: owned shutdown failed; business outcome unchanged."); } + } + SchemaExecutor getSchemaExecutor() { return schemaExecutor; } @@ -149,7 +161,16 @@ GeneratedSchemaBootstrap getGeneratedSchemaBootstrap() { } public void recordExecutionMetadata(UserContext context, ExecutionMetadata metadata) { - if (logSink != null) { + if (metadata.getSqlExecutionCorrelation() != null) { + try { + telemetry.recordDiagnostic(SqlDiagnosticEvent.project(metadata)); + } catch (Throwable ignored) { + // No unsafe fallback: exporting diagnostics never changes business behavior. + } + } + boolean logging = metadata.getOperation() == DataServiceOperation.QUERY + ? isQueryExecutionLoggingEnabled() : isMutationExecutionLoggingEnabled(); + if (logging && logSink != null) { try { logSink.writeExecutionLog(context, LogPrivacy.sql(metadata, requiresSensitiveSqlLogData())); @@ -160,6 +181,23 @@ public void recordExecutionMetadata(UserContext context, ExecutionMetadata metad } } + io.teaql.core.SqlStatementObservation startSqlStatement(String backend, DataServiceOperation operation) { + try { if (!telemetry.recordsSqlDiagnostics()) return io.teaql.core.SqlStatementObservation.NOOP; } + catch (Throwable ignored) { return io.teaql.core.SqlStatementObservation.NOOP; } + String family = operation == null ? "unknown" : operation.name().toLowerCase(java.util.Locale.ROOT); + var scope = RuntimeTelemetry.startSafely(telemetry, new RuntimeTelemetry.Operation("provider", backend + ".statement", + Map.of("teaql.provider.kind", backend, "teaql.provider.operation", family, + "teaql.provider.execution", "statement"))); + return new io.teaql.core.SqlStatementObservation() { + @Override public boolean enabled() { return true; } + @Override public io.teaql.core.SqlExecutionCorrelation correlation() { return scope.correlation(); } + @Override public void success() { scope.success(); } + @Override public void failure(Throwable failure) { scope.failure(failure); } + @Override public void detach() { scope.detach(); } + @Override public Activation activate() { var activation = scope.activate(); return activation::close; } + }; + } + @SuppressWarnings("unchecked") public SmartList executeForList(UserContext context, SearchRequest request) { RuntimeTelemetry.Scope telemetryScope = RuntimeTelemetry.startSafely(telemetry, @@ -167,11 +205,39 @@ public SmartList executeForList(UserContext context, Searc Map.of("teaql.entity.type", request.getTypeName()))); try { QueryIntent intent = QueryIntent.of(request.comment(), request.purpose()); + var requestScope = QueryRequestScope.capture(request); enforceMaterializedLimit(request, request.hardLimit()); if (queryPolicy != null) { queryPolicy.enforceSelect(context, request); } + // Existing customer policies may use generated typed request methods. + // Freeze their result only after that hook, while the intent header was + // captured beforehand and cannot be overwritten by the policy. + request = new QueryRequestSnapshot<>(request, requestScope); + QueryOperation countOperation = requestedListCount(request); + SearchRequest countRequest = null; + String countAlias = null; + if (countOperation != null) { + countAlias = countOperation.alias() == null ? TeaQLConstants.ROOT_LIST_PARAMETER_NAME : countOperation.alias(); + BaseRequest snapshot = (BaseRequest) request; + countRequest = new QueryRequestSnapshot(snapshot.internalCountRequest(countAlias), + requestScope.deriveOperation(request.getTypeName(), countOperation), request); + String alias = countAlias; + snapshot.getAggregations().getAggregates().removeIf(aggregate -> alias.equals(aggregate.name())); + } SmartList result = executeForListResolved(context, request, intent); + if (countRequest != null && !(result.aggregationNumberProperties().get(countAlias) instanceof Number)) { + EntityDescriptor descriptor = metadata.resolveEntityDescriptor(request.getTypeName()); + String route = descriptor.getDataService(); + if (route == null || route.isEmpty()) route = "default"; + QueryResult count = registry.resolveQueryExecutor(route).query(context, new DefaultQueryRequest(countRequest, intent)); + if (!(count instanceof DefaultQueryResult counted) || counted.getAggregationResult() == null) { + throw new TeaQLRuntimeException("Requested list count is not supported for route: " + route); + } + if (result.isSharedEmpty()) result = new SmartList<>(); + result.addAggregationResult(context, counted.getAggregationResult()); + } + if (countRequest != null) result.__internalSetPageCountAlias(countAlias); telemetryScope.success(Map.of("teaql.result.cardinality", result.size())); return result; } catch (RuntimeException | Error error) { @@ -180,53 +246,37 @@ public SmartList executeForList(UserContext context, Searc } } - @SuppressWarnings("unchecked") - public SmartList executeForPage( - UserContext context, SearchRequest request, int offset, int limit) { - QueryIntent intent = QueryIntent.of(request.comment(), request.purpose()); - if (!(request instanceof BaseRequest baseRequest)) { - throw new TeaQLRuntimeException("Paged execution requires a generated BaseRequest"); - } - baseRequest.offset(offset, limit); - SmartList rows = executeForList(context, request); - Object idSetAccuracy = context.getAttribute("teaql.idSet.countAccuracy"); - Object idSetCount = context.getAttribute("teaql.idSet.count"); - if ("EXACT".equals(idSetAccuracy) && idSetCount instanceof Number exactCount) { - if (rows.isSharedEmpty()) rows = new SmartList<>(); - AggregationItem item = new AggregationItem(); - item.setValues(Map.of( - new SimpleNamedExpression(TeaQLConstants.ROOT_LIST_PARAMETER_NAME), - exactCount.longValue())); - AggregationResult total = new AggregationResult(); - total.setData(List.of(item)); - rows.addAggregationResult(context, total); - return rows; - } - SearchRequest countRequest = baseRequest.internalCountRequest(); - EntityDescriptor descriptor = metadata.resolveEntityDescriptor(request.getTypeName()); - String route = descriptor.getDataService(); - if (route == null || route.isEmpty()) route = "default"; - QueryExecutor executor = registry.resolveQueryExecutor(route); - QueryResult countResult = executor.query(context, new DefaultQueryRequest(countRequest, intent)); - if (!(countResult instanceof DefaultQueryResult result) - || result.getAggregationResult() == null) { - throw new TeaQLRuntimeException("Exact page count is not supported for route: " + route); - } - if (rows.isSharedEmpty()) { - rows = new SmartList<>(); - } - rows.addAggregationResult(context, result.getAggregationResult()); - return rows; + /** A declared ungrouped count accompanies rows through the normal list entry. + * Grouped/mixed aggregate requests retain their existing aggregate semantics. */ + private static QueryOperation requestedListCount(SearchRequest request) { + QueryOperation operation = request.pageCountOperation(); + if (operation == null || !(request instanceof BaseRequest)) return null; + var aggregations = request.getAggregations(); + if (aggregations == null || !aggregations.getSimpleDimensions().isEmpty() + || !aggregations.getComplexDimensions().isEmpty() || aggregations.getAggregates().size() != 1) return null; + String alias = operation.alias() == null ? TeaQLConstants.ROOT_LIST_PARAMETER_NAME : operation.alias(); + var aggregate = aggregations.getAggregates().get(0); + return alias.equals(aggregate.name()) && aggregate.getExpression() instanceof AggrExpression expression + && expression.getOperator() == AggrFunction.COUNT ? operation : null; } /** Executes a business-facing streaming query after the same policy gate as list queries. */ public Stream executeForStream( UserContext context, SearchRequest request) { + RuntimeTelemetry.Scope scope = RuntimeTelemetry.startSafely(telemetry, + new RuntimeTelemetry.Operation("query", request.getTypeName() + ".stream", + Map.of("teaql.entity.type", request.getTypeName()))); + try { QueryIntent intent = QueryIntent.of(request.comment(), request.purpose()); + var requestScope = QueryRequestScope.capture(request); if (queryPolicy != null) { queryPolicy.enforceSelect(context, request); } - return executeForStreamResolved(context, request, intent); + request = new QueryRequestSnapshot<>(request, requestScope); + return RuntimeQueryStream.wrap(executeForStreamResolved(context, request, intent), scope); + } catch (RuntimeException | Error failure) { + scope.failure(failure); throw failure; + } } /** @@ -236,9 +286,11 @@ public Stream executeForStream( public Stream internalExecuteForStream( UserContext context, SearchRequest request) { QueryIntent intent = requireInheritedQueryIntent(request); + var requestScope = QueryRequestScope.capture(request); if (queryPolicy != null) { queryPolicy.enforceSelect(context, request); } + request = new QueryRequestSnapshot<>(request, requestScope); return executeForStreamResolved(context, request, intent); } @@ -314,6 +366,9 @@ private static void enforceMaterializedLimit(SearchRequest request, int hardL if (slice == null) { throw new TeaQLRuntimeException("[QUERY HARD LIMIT] An unlimited materialized query is not allowed"); } + if (slice.getOffset() < 0) { + throw new IllegalArgumentException("offset must not be negative"); + } int requested = slice.getSize(); if (requested <= 0) { slice.setSize(hardLimit); @@ -1301,7 +1356,7 @@ public static class Builder { // excludes bind values and rendered Debug SQL. private boolean queryExecutionLoggingEnabled = true; private boolean mutationExecutionLoggingEnabled = true; - private RuntimeTelemetry telemetry = RuntimeTelemetry.NOOP; + private RuntimeTelemetry telemetry; private SchemaExecutor schemaExecutor; private BusinessIdService businessIdService; private BusinessIdSchemaContributor businessIdSchemaContributor; diff --git a/teaql-runtime/src/main/java/io/teaql/runtime/TelemetryStartup.java b/teaql-runtime/src/main/java/io/teaql/runtime/TelemetryStartup.java new file mode 100644 index 00000000..cee2f09a --- /dev/null +++ b/teaql-runtime/src/main/java/io/teaql/runtime/TelemetryStartup.java @@ -0,0 +1,70 @@ +package io.teaql.runtime; + +import java.net.URI; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public final class TelemetryStartup { + public static final String ENABLED = "TEAQL_TELEMETRY_ENABLED"; + public static final String SERVICE_NAME = "TEAQL_TELEMETRY_SERVICE_NAME"; + public static final String ENDPOINT = "TEAQL_TELEMETRY_OTLP_ENDPOINT"; + private static final String HINT = ENABLED + "=true " + SERVICE_NAME + "= " + + ENDPOINT + "="; + private static final Set ANNOUNCED = ConcurrentHashMap.newKeySet(); + + private TelemetryStartup() {} + + public record Configuration(boolean enabled, String serviceName, URI endpoint, String error) { + public String message() { + if (error != null) return "TeaQL telemetry: disabled; configuration=" + error + ". " + HINT; + if (!enabled) return "TeaQL telemetry: supported, disabled. Enable full masked diagnostics with " + HINT; + return "TeaQL telemetry: starting standard module; full recording, masked SQL; " + + ENABLED + "=true " + SERVICE_NAME + "=" + serviceName + " " + ENDPOINT + "=" + endpoint; + } + } + + public static Configuration read(Map environment) { + String flag = environment.getOrDefault(ENABLED, "").trim(); + if (flag.isEmpty() || flag.equalsIgnoreCase("false")) return new Configuration(false, null, null, null); + if (!flag.equalsIgnoreCase("true")) return new Configuration(false, null, null, "invalid-enable"); + String service = environment.getOrDefault(SERVICE_NAME, "").trim(); + if (!service.matches("[a-zA-Z0-9_.-]{1,120}")) return new Configuration(false, null, null, "invalid-service"); + try { + URI endpoint = URI.create(environment.getOrDefault(ENDPOINT, "")); + if (!("http".equals(endpoint.getScheme()) || "https".equals(endpoint.getScheme())) + || endpoint.getHost() == null || endpoint.getUserInfo() != null + || endpoint.getQuery() != null || endpoint.getFragment() != null) { + throw new IllegalArgumentException(); + } + return new Configuration(true, service, endpoint, null); + } catch (RuntimeException ignored) { return new Configuration(false, null, null, "invalid-endpoint"); } + } + + public static void notice(String message) { + try { System.getLogger("io.teaql.runtime.telemetry").log(System.Logger.Level.INFO, message); } + catch (Throwable ignored) { /* diagnostic output must not change startup */ } + } + + static RuntimeTelemetry initialize(Map environment, ClassLoader loader) { + Configuration config = read(environment); + if (ANNOUNCED.add(config.message())) notice(config.message()); + if (!config.enabled()) return RuntimeTelemetry.NOOP; + try { + StandardTelemetryFactory factory = ServiceLoader.load(StandardTelemetryFactory.class, loader) + .findFirst().orElse(null); + if (factory == null) { + notice("TeaQL telemetry: standard module unavailable; install teaql-opentelemetry. " + HINT); + return RuntimeTelemetry.NOOP; + } + RuntimeTelemetry telemetry = factory.create(config.serviceName(), config.endpoint()); + if (telemetry == null) throw new IllegalStateException(); + notice("TeaQL telemetry: enabled; full recording, traces, metrics, logs and masked SQL."); + return telemetry; + } catch (Throwable ignored) { + notice("TeaQL telemetry: standard module initialization failed; business continues without export. " + HINT); + return RuntimeTelemetry.NOOP; + } + } +} diff --git a/teaql-runtime/src/main/java/module-info.java b/teaql-runtime/src/main/java/module-info.java index 0acd8b13..d4858673 100644 --- a/teaql-runtime/src/main/java/module-info.java +++ b/teaql-runtime/src/main/java/module-info.java @@ -1,6 +1,7 @@ module io.teaql.runtime { requires io.teaql.core; requires io.teaql.utils; + requires com.fasterxml.jackson.databind; exports io.teaql.runtime; exports io.teaql.runtime.memory; @@ -9,4 +10,5 @@ exports io.teaql.runtime.reference; provides io.teaql.core.spi.ContextAssembler with io.teaql.runtime.boot.CoreAssembler; + uses io.teaql.runtime.StandardTelemetryFactory; } diff --git a/teaql-runtime/src/test/java/io/teaql/runtime/QueryRequestTreeTest.java b/teaql-runtime/src/test/java/io/teaql/runtime/QueryRequestTreeTest.java new file mode 100644 index 00000000..2a6cdd97 --- /dev/null +++ b/teaql-runtime/src/test/java/io/teaql/runtime/QueryRequestTreeTest.java @@ -0,0 +1,86 @@ +package io.teaql.runtime; + +import io.teaql.core.*; +import org.junit.Test; +import static org.junit.Assert.*; +import java.util.List; + +public class QueryRequestTreeTest { + static class Request extends BaseRequest { + private final String type; + Request(String type, String comment, String purpose) { + super(TeaQLRuntimeTest.DummyEntity.class, TeaQLRuntimeTest.DummyEntity::new); + this.type = type; internalComment(comment); internalPurpose(purpose); + } + @Override public String getTypeName() { return type; } + } + private DefaultQueryRequest root() { return new DefaultQueryRequest(new Request("School", "load schools", "render school list")); } + private List comments(DefaultQueryRequest request) { return request.businessTraceChain().stream().map(TraceNode::getComment).toList(); } + private QueryOperation relation() { return QueryOperation.nativeMethod(QueryOperation.Kind.RELATION_SELECT, "selectSchoolTypeWith", null, "schoolType"); } + + @Test public void childResolvesDirectParentAndRootWithoutCopyingParentBody() { + var root = root(); + var child = root.derive(new Request("SchoolType", "load visible types", null), relation()); + assertSame(root.requestScope(), child.parentRequest().parentRequest()); + assertSame(root.requestScope(), child.rootRequest()); + assertEquals(List.of("render school list", "load schools", "selectSchoolTypeWith(...)", "load visible types"), comments(child)); + assertEquals("load schools", child.comment()); + assertEquals("load visible types", child.localComment()); + } + @Test public void defaultsCannotAuthorizeAnInvalidRootOrReplaceExplicitBlank() { + assertEquals("load SchoolType", root().derive(new Request("SchoolType", null, null), relation()).localComment()); + assertThrows(RequestIntentException.class, () -> new DefaultQueryRequest(new Request("School", null, "render schools"))); + for (String blank : List.of("", " \n ", "\u0085")) + assertThrows(RequestIntentException.class, () -> root().derive(new Request("SchoolType", blank, null), relation())); + } + @Test public void replacementAndRecaptureRetainSharedHeaderAndRoot() { + var root = root(); var child = root.derive(new Request("SchoolType", "load types", null), relation()); + var copy = child.withQuery(new Request("SchoolType", null, null)); + assertSame(child.requestScope(), copy.requestScope()); + assertSame(root.requestScope(), new DefaultQueryRequest(copy.getSearchRequest()).rootRequest()); + } + @Test public void sharedTemplateGetsDistinctOwnersAndMutableContainersAreCopied() { + var template = new Request("SchoolType", "load types", null); + template.getSlice().setSize(9); template.selectProperty("name"); + var first = root().derive(template, relation()); var second = root().derive(template, relation()); + assertNotSame(first.rootRequest(), second.rootRequest()); + first.getSearchRequest().getSlice().setSize(3); + ((BaseRequest) first.getSearchRequest()).selectProperty("id"); + assertEquals(9, template.getSlice().getSize()); + assertEquals(9, second.getSearchRequest().getSlice().getSize()); + assertEquals(1, template.getProjections().size()); assertNull(template.requestScope()); + } + @Test public void unrelatedInvocationCannotReplaceOwnedQueryEvenWithIdenticalIntent() { + var first = root(); var second = root(); + assertThrows(IllegalArgumentException.class, () -> first.withQuery(second.getSearchRequest())); + var ownCopy = first.withQuery(first.getSearchRequest()); + assertSame(first.requestScope(), ownCopy.withQuery(first.getSearchRequest()).requestScope()); + assertThrows(IllegalArgumentException.class, () -> ownCopy.withQuery(second.getSearchRequest())); + } + @Test public void countAliasUsesCentralJavaLiteralRenderer() { + var child = root().deriveOperation(new Request("School", null, null), + QueryOperation.nativeMethod(QueryOperation.Kind.NAMED_COUNT, "countAs", "total\"\\schools\n", null)); + assertEquals("countAs(\"total\\\"\\\\schools\\n\")", child.localComment()); + assertTrue(child.getSearchRequest().sqlTraceSource().isEmpty()); + } + @Test public void prefixesAreOrdinaryTextAndNoOwnerAppearsInQueryExtensions() { + var request = new DefaultQueryRequest(new Request("School", "what: load schools", "why: render schools")); + assertEquals(List.of("why: render schools", "what: load schools"), comments(request)); + assertTrue(request.getSearchRequest().getExtensions().isEmpty()); + } + @Test public void relationHelpersRetainCallerSqlAliasesWithoutMutatingTemplates() { + var parent = new Request("School", "load schools", "render schools"); + var template = new Request("SchoolType", "load types", null); + template.selectProperty("name"); + parent.addRelationCount("typeCount", template, "school", QueryOperation.nativeMethod( + QueryOperation.Kind.RELATION_COUNT, "countSchoolTypesAs", "typeCount", "types")); + parent.addRelationScalarAggregate("typeTotal", template, "school", "amount", AggrFunction.SUM, + QueryOperation.nativeMethod(QueryOperation.Kind.AGGREGATE, "sumAmountOfSchoolTypesAs", "typeTotal", "types")); + var count = parent.getDynamicAggregateAttributes().get(0).getAggregateRequest(); + var sum = parent.getDynamicAggregateAttributes().get(1).getAggregateRequest(); + assertEquals("typeCount", count.getAggregations().getAggregates().get(0).name()); + assertEquals("typeTotal", sum.getAggregations().getAggregates().get(0).name()); + assertTrue(template.getAggregations().getAggregates().isEmpty()); + assertNull(template.getPartitionProperty()); assertEquals(1, template.getProjections().size()); + } +} diff --git a/teaql-runtime/src/test/java/io/teaql/runtime/RequestIntentGateTest.java b/teaql-runtime/src/test/java/io/teaql/runtime/RequestIntentGateTest.java index 9ecc234b..27d8522a 100644 --- a/teaql-runtime/src/test/java/io/teaql/runtime/RequestIntentGateTest.java +++ b/teaql-runtime/src/test/java/io/teaql/runtime/RequestIntentGateTest.java @@ -85,10 +85,15 @@ private static void requiredIntent(String code, String kind, Runnable action) { String code = missingComment ? "REQUEST_COMMENT_REQUIRED" : "QUERY_PURPOSE_REQUIRED"; var query = request(missingComment ? blank : "load SECRET-CANARY", missingComment ? "render SECRET-CANARY" : blank); + var child = request("load related objects", "render related objects"); + query.enhanceRelation("related", child); + assertEquals("load related objects", child.comment()); + assertEquals("render related objects", child.purpose()); requiredIntent(code, "query", () -> runtime.executeForList(context, query)); requiredIntent(code, "query", () -> runtime.executeForStream(context, query)); requiredIntent(code, "query", () -> runtime.aggregation(context, query)); - requiredIntent(code, "query", () -> runtime.executeForPage(context, query, 0, 10)); + query.offset(0, 10).count(); + requiredIntent(code, "query", () -> runtime.executeForList(context, query)); } var entity = new TeaQLRuntimeTest.DummyEntity(); entity.setComment(blank); @@ -100,6 +105,11 @@ private static void requiredIntent(String code, String kind, Runnable action) { assertTrue(sink.executions.isEmpty()); assertTrue(sink.auditEvents.isEmpty()); assertTrue(sink.governanceEvents.isEmpty()); + runtime.executeForList(context, request("load validation control", "verify live provider probes")); + assertTrue(queryPolicies.get() > 0); + assertEquals(1, provider.calls.get()); + System.out.println("RTC_COM_01_PASS logging=" + logging + + " fields=comment,purpose invalid_provider=0 invalid_policy=0 annotated_child=true live_control=true"); } } @@ -143,7 +153,8 @@ private static void requiredIntent(String code, String kind, Runnable action) { requiredIntent("REQUEST_COMMENT_REQUIRED", "query", () -> runtime.executeForList(context, query)); requiredIntent("REQUEST_COMMENT_REQUIRED", "query", () -> runtime.executeForStream(context, query)); requiredIntent("REQUEST_COMMENT_REQUIRED", "query", () -> runtime.aggregation(context, query)); - requiredIntent("REQUEST_COMMENT_REQUIRED", "query", () -> runtime.executeForPage(context, query, 0, 10)); + query.offset(0, 10).count(); + requiredIntent("REQUEST_COMMENT_REQUIRED", "query", () -> runtime.executeForList(context, query)); var entity = new TeaQLRuntimeTest.DummyEntity(); entity.__internalSet("id", 801L); entity.__internalSet("version", 1L); @@ -245,7 +256,7 @@ private static void requiredIntent(String code, String kind, Runnable action) { required("REQUEST_COMMENT_REQUIRED", () -> runtime.executeForList(context, request(null, "render orders"))); required("REQUEST_COMMENT_REQUIRED", () -> runtime.executeForStream(context, request(null, "render orders"))); required("REQUEST_COMMENT_REQUIRED", () -> runtime.aggregation(context, request(null, "render orders"))); - required("REQUEST_COMMENT_REQUIRED", () -> runtime.executeForPage(context, request(null, "render orders"), 0, 10)); + required("REQUEST_COMMENT_REQUIRED", () -> runtime.executeForList(context, request(null, "render orders").offset(0, 10).count())); assertEquals(0, policyCalls.get()); assertEquals(0, provider.calls.get()); } diff --git a/teaql-runtime/src/test/java/io/teaql/runtime/RequestedListCountTest.java b/teaql-runtime/src/test/java/io/teaql/runtime/RequestedListCountTest.java new file mode 100644 index 00000000..2cf4eacb --- /dev/null +++ b/teaql-runtime/src/test/java/io/teaql/runtime/RequestedListCountTest.java @@ -0,0 +1,102 @@ +package io.teaql.runtime; + +import io.teaql.core.*; +import io.teaql.core.criteria.Operator; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import static org.junit.Assert.*; + +public class RequestedListCountTest { + private BaseRequest request() { + var request = new BaseRequest(TeaQLRuntimeTest.DummyEntity.class) { + { internalComment("load matching records"); internalPurpose("render bounded list"); } + @Override public String getTypeName() { return "Dummy"; } + }; + request.offset(2, 2); request.count(); + return request; + } + + @Test public void ordinaryListOwnsRowsAndRequestedCountUnderOneBalancedQuery() { + var executor = new TeaQLRuntimeTest.PageQueryExecutor(); + List events = new ArrayList<>(); + RuntimeTelemetry telemetry = operation -> { + events.add("start:" + operation.family()); + return new RuntimeTelemetry.Scope() { + public void success(Map attributes) { events.add("success:" + operation.family()); } + public void failure(Throwable error) { events.add("failure:" + operation.family()); } + }; + }; + var runtime = TeaQLRuntime.builder().metadata(new TeaQLRuntimeTest.DummyMetaFactory()) + .dataService("dummy", executor).telemetry(telemetry) + .queryPolicy(new QueryPolicy() { + @Override public void enforceSelect(UserContext context, SearchRequest query) { + var current = (BaseRequest) query; + current.appendSearchCriteria(current.createBasicSearchCriteria("status", Operator.EQUAL, "ACTIVE")); + } + }).build(); + var source = request(); + var rows = runtime.executeForList(new DefaultUserContext(runtime), source); + assertEquals(List.of(3L, 4L), rows.toList(Entity::getId)); + assertEquals(5, rows.getTotalCount()); assertEquals(2, executor.requests.size()); + assertFalse(executor.requests.get(0).hasSimpleAgg()); assertTrue(executor.requests.get(1).hasSimpleAgg()); + assertSame(executor.requests.get(0).getSearchCriteria(), executor.requests.get(1).getSearchCriteria()); + var chain = new DefaultQueryRequest(executor.requests.get(1)).businessTraceChain(); + assertEquals("count()", chain.get(chain.size() - 1).getComment()); + assertEquals(List.of("start:query", "start:provider", "success:provider", "success:query"), events); + assertEquals("caller count configuration is not removed", 1, source.getAggregations().getAggregates().size()); + assertEquals(2, source.getSlice().getOffset()); assertEquals(2, source.getSlice().getSize()); + } + + @Test public void groupedCountRetainsAggregateSemanticsInsteadOfCreatingAnotherRowsQuery() { + var executor = new TeaQLRuntimeTest.PageQueryExecutor(); + var runtime = TeaQLRuntime.builder().metadata(new TeaQLRuntimeTest.DummyMetaFactory()).dataService("dummy", executor).build(); + var source = request(); + source.getAggregations().getSimpleDimensions().add(new SimpleNamedExpression("name", new PropertyReference("name"))); + runtime.executeForList(new DefaultUserContext(runtime), source); + assertEquals(1, executor.requests.size()); assertTrue(executor.requests.get(0).hasSimpleAgg()); + assertEquals(1, executor.requests.get(0).getAggregations().getSimpleDimensions().size()); + } + + @Test public void noSeparatePageExecutionEntryRemainsInCoreOrRuntime() { + for (Class type : List.of(ExecutableRequest.class, UserContext.class, DefaultUserContext.class, TeaQLRuntime.class)) { + assertFalse(type.getName(), java.util.Arrays.stream(type.getMethods()) + .anyMatch(method -> method.getName().equals("executeForPage"))); + } + } + + @Test public void staleContextIdSetDiagnosticsCannotBecomeAnotherRequestsTotal() { + var executor = new TeaQLRuntimeTest.PageQueryExecutor(); + var runtime = TeaQLRuntime.builder().metadata(new TeaQLRuntimeTest.DummyMetaFactory()).dataService("dummy", executor).build(); + var context = new DefaultUserContext(runtime); + context.putAttribute("teaql.idSet.countAccuracy", "EXACT"); + context.putAttribute("teaql.idSet.count", 999L); + var source = request(); + source.optimizePaginationWithIdSet("another-query", 60, 100); + assertEquals(5, runtime.executeForList(context, source).getTotalCount()); + assertEquals(2, executor.requests.size()); + } + + @Test public void listWithoutExplicitCountDoesNotAddAnAggregateQuery() { + var executor = new TeaQLRuntimeTest.PageQueryExecutor(); + var runtime = TeaQLRuntime.builder().metadata(new TeaQLRuntimeTest.DummyMetaFactory()).dataService("dummy", executor).build(); + var source = request(); + source.getAggregations().getAggregates().clear(); + runtime.executeForList(new DefaultUserContext(runtime), source); + assertEquals(1, executor.requests.size()); + assertFalse(executor.requests.get(0).hasSimpleAgg()); + } + + @Test public void negativeOffsetIsRejectedBeforePolicyOrProvider() { + var executor = new TeaQLRuntimeTest.PageQueryExecutor(); + int[] policies = {0}; + var runtime = TeaQLRuntime.builder().metadata(new TeaQLRuntimeTest.DummyMetaFactory()).dataService("dummy", executor) + .queryPolicy(new QueryPolicy() { + @Override public void enforceSelect(UserContext context, SearchRequest request) { policies[0]++; } + }).build(); + var source = request(); source.offset(-1, 2); + assertThrows(IllegalArgumentException.class, () -> runtime.executeForList(new DefaultUserContext(runtime), source)); + assertEquals(0, policies[0]); assertTrue(executor.requests.isEmpty()); + } +} diff --git a/teaql-runtime/src/test/java/io/teaql/runtime/RuntimeQueryStreamTest.java b/teaql-runtime/src/test/java/io/teaql/runtime/RuntimeQueryStreamTest.java new file mode 100644 index 00000000..de5d3f66 --- /dev/null +++ b/teaql-runtime/src/test/java/io/teaql/runtime/RuntimeQueryStreamTest.java @@ -0,0 +1,67 @@ +package io.teaql.runtime; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; +import org.junit.Test; +import static org.junit.Assert.*; + +public class RuntimeQueryStreamTest { + private static final class Scope implements RuntimeTelemetry.Scope { + final List events = new ArrayList<>(); + int activations; + Map completion; + Throwable failure; + public void success(Map values) { events.add("success"); completion = values; } + public void failure(Throwable error) { events.add("failure"); failure = error; } + public void detach() { events.add("detach"); } + public RuntimeTelemetry.PropagationScope activate() { + activations++; return () -> activations--; + } + } + + @Test public void exhaustedCursorHasOneCompletionDeliveredCountAndNoLeakedActivation() { + var scope = new Scope(); var closed = new AtomicInteger(); + try (var stream = RuntimeQueryStream.wrap(Stream.of(1, 2, 3).onClose(closed::incrementAndGet), scope)) { + assertEquals(List.of("detach"), scope.events); + assertEquals(3, stream.count()); + } + assertEquals(List.of("detach", "success"), scope.events); + assertEquals(3L, scope.completion.get("teaql.result.cardinality")); + assertEquals(1, closed.get()); assertEquals(0, scope.activations); + } + + @Test public void shortCircuitAndUnconsumedCloseAreCancellationNotSuccess() { + for (boolean consume : List.of(false, true)) { + var scope = new Scope(); var closed = new AtomicInteger(); + var stream = RuntimeQueryStream.wrap(Stream.of(1, 2).onClose(closed::incrementAndGet), scope); + if (consume) assertEquals(1, stream.findFirst().get().intValue()); + stream.close(); stream.close(); + assertEquals(List.of("detach", "failure"), scope.events); + assertEquals("cancelled", RuntimeTelemetry.errorCategory(scope.failure)); + assertEquals(1, closed.get()); assertEquals(0, scope.activations); + } + } + + @Test public void originalTraversalFailurePreservedAndCloseFailureSuppressedOnce() { + var original = new IllegalStateException("private traversal canary"); + var closeFailure = new IllegalArgumentException("private close canary"); + var scope = new Scope(); + try (var stream = RuntimeQueryStream.wrap(Stream.of(1).onClose(() -> { throw closeFailure; }), scope)) { + var observed = assertThrows(IllegalStateException.class, () -> stream.forEach(value -> { throw original; })); + assertSame(original, observed); assertArrayEquals(new Throwable[]{closeFailure}, observed.getSuppressed()); + } + assertSame(original, scope.failure); assertEquals(0, scope.activations); + assertEquals(List.of("detach", "failure"), scope.events); + } + + @Test public void closeFailureWithoutTraversalIsFailureNotCancellation() { + var failure = new IllegalStateException("private close canary"); + var scope = new Scope(); + var stream = RuntimeQueryStream.wrap(Stream.of(1).onClose(() -> { throw failure; }), scope); + assertSame(failure, assertThrows(IllegalStateException.class, stream::close)); + assertSame(failure, scope.failure); assertEquals(0, scope.activations); + } +} diff --git a/teaql-runtime/src/test/java/io/teaql/runtime/TeaQLRuntimeTest.java b/teaql-runtime/src/test/java/io/teaql/runtime/TeaQLRuntimeTest.java index 3e729441..ba44096a 100644 --- a/teaql-runtime/src/test/java/io/teaql/runtime/TeaQLRuntimeTest.java +++ b/teaql-runtime/src/test/java/io/teaql/runtime/TeaQLRuntimeTest.java @@ -625,8 +625,8 @@ public void pagedExecutionReturnsRowsAndExactPolicyFilteredTotal() { { internalComment("list active"); internalPurpose("test exact page total"); } @Override public String getTypeName() { return "Dummy"; } }; - SmartList page = runtime.executeForPage( - new DefaultUserContext(runtime), request, 2, 2); + request.offset(2, 2).count(); + SmartList page = runtime.executeForList(new DefaultUserContext(runtime), request); Assert.assertEquals(List.of(3L, 4L), page.toList(Entity::getId)); Assert.assertEquals(5, page.getTotalCount()); Assert.assertEquals(2, executor.requests.size()); @@ -665,8 +665,10 @@ public void streamingExecutionAppliesRequestPolicyBeforeProvider() { } Assert.assertEquals(1, policyCalls.get()); - Assert.assertSame(request, executor.request); + Assert.assertNotSame(request, executor.request); Assert.assertNotNull(executor.request.getSearchCriteria()); + Assert.assertSame("typed policy enrichment is retained in the frozen execution", request.getSearchCriteria(), executor.request.getSearchCriteria()); + Assert.assertEquals(request.comment(), executor.request.inheritedQueryIntent().comment()); } @Test @@ -702,7 +704,8 @@ public void internalStreamingRequiresAuthorizedRootAndAppliesRequestPolicy() { context.popTrace(); } Assert.assertEquals(1, policyCalls.get()); - Assert.assertSame(scoped, executor.request); + Assert.assertNotSame(scoped, executor.request); + Assert.assertEquals(scoped.inheritedQueryIntent(), executor.request.inheritedQueryIntent()); } @Test diff --git a/teaql-runtime/src/test/java/io/teaql/runtime/TelemetryStartupTest.java b/teaql-runtime/src/test/java/io/teaql/runtime/TelemetryStartupTest.java new file mode 100644 index 00000000..50bf597b --- /dev/null +++ b/teaql-runtime/src/test/java/io/teaql/runtime/TelemetryStartupTest.java @@ -0,0 +1,36 @@ +package io.teaql.runtime; + +import java.util.Map; +import org.junit.Test; +import static org.junit.Assert.*; + +public class TelemetryStartupTest { + @Test public void disabledShowsAllVariablesWithoutEchoingUnusedValues() { + var config = TelemetryStartup.read(Map.of(TelemetryStartup.SERVICE_NAME, "PRIVATE-SERVICE")); + assertFalse(config.enabled()); + String message = config.message(); + assertTrue(message.contains(TelemetryStartup.ENABLED)); + assertTrue(message.contains(TelemetryStartup.SERVICE_NAME)); + assertTrue(message.contains(TelemetryStartup.ENDPOINT)); + assertFalse(message.contains("PRIVATE-SERVICE")); + } + @Test public void credentialsAndTokenURLsAreRejectedWithoutEchoingThem() { + for (String url : new String[]{"http://SECRET:password@localhost:4318", "http://localhost:4318?token=SECRET", "file:///SECRET"}) { + var config = TelemetryStartup.read(Map.of(TelemetryStartup.ENABLED, "true", + TelemetryStartup.SERVICE_NAME, "orders", TelemetryStartup.ENDPOINT, url)); + assertFalse(config.enabled()); assertEquals("invalid-endpoint", config.error()); + assertFalse(config.message().contains("SECRET")); + } + } + @Test public void validConfigurationIsFullRecordingAndDisabledDoesNotLoadFactory() { + var config = TelemetryStartup.read(Map.of(TelemetryStartup.ENABLED, "true", TelemetryStartup.SERVICE_NAME, + "orders", TelemetryStartup.ENDPOINT, "http://localhost:4318")); + assertTrue(config.enabled()); assertTrue(config.message().contains("full recording")); + ClassLoader forbidden = new ClassLoader(null) { + @Override public java.util.Enumeration getResources(String name) { + throw new AssertionError("Disabled mode loaded an optional factory"); + } + }; + assertSame(RuntimeTelemetry.NOOP, TelemetryStartup.initialize(Map.of(), forbidden)); + } +} diff --git a/teaql-snowflake/pom.xml b/teaql-snowflake/pom.xml index 936881d1..3f37a939 100644 --- a/teaql-snowflake/pom.xml +++ b/teaql-snowflake/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-snowflake teaql-snowflake diff --git a/teaql-sql-portable/pom.xml b/teaql-sql-portable/pom.xml index a05b7f93..41eff8c7 100644 --- a/teaql-sql-portable/pom.xml +++ b/teaql-sql-portable/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-sql-portable diff --git a/teaql-sql-portable/src/main/java/io/teaql/core/internal/TempRequest.java b/teaql-sql-portable/src/main/java/io/teaql/core/internal/TempRequest.java index ca98ad7e..2a28f9cd 100644 --- a/teaql-sql-portable/src/main/java/io/teaql/core/internal/TempRequest.java +++ b/teaql-sql-portable/src/main/java/io/teaql/core/internal/TempRequest.java @@ -6,7 +6,7 @@ import io.teaql.core.SearchRequest; import io.teaql.core.QueryIntent; -public class TempRequest extends BaseRequest { +public class TempRequest extends io.teaql.core.QueryRequestSnapshot { String type; private QueryIntent rootIntent; private SearchRequest original; @@ -16,11 +16,10 @@ public TempRequest(SearchRequest request) { } public TempRequest(SearchRequest request, QueryIntent rootIntent) { - super(request.returnType()); + super(request, request.requestScope()); this.original = request; this.rootIntent = rootIntent; type = request.getTypeName(); - copy(request); this.comment = rootIntent == null ? request.comment() : rootIntent.comment(); this.purpose = rootIntent == null ? request.purpose() : rootIntent.purpose(); } @@ -34,35 +33,10 @@ public TempRequest(SearchRequest request, QueryIntent rootIntent) { } public TempRequest(Class returnType, String typeName) { - super(returnType); + super(returnType, typeName); type = typeName; } - private void copy(SearchRequest pRequest) { - projections.addAll(pRequest.getProjections()); - simpleDynamicProperties.addAll(pRequest.getSimpleDynamicProperties()); - dynamicPropertyMetadata = pRequest.getDynamicPropertyMetadata(); - searchCriteria = pRequest.getSearchCriteria(); - orderBys = pRequest.getOrderBy(); - slice = pRequest.getSlice(); - enhanceRelations = pRequest.enhanceRelations(); - partitionProperty = pRequest.getPartitionProperty(); - aggregations = pRequest.getAggregations(); - propagateAggregations = pRequest.getPropagateAggregations(); - propagateDimensions = pRequest.getPropagateDimensions(); - dynamicAggregateAttributes = pRequest.getDynamicAggregateAttributes(); - enhanceChildren = pRequest.enhanceChildren(); - cacheAggregation = pRequest.tryCacheAggregation(); - aggregateCacheTime = pRequest.getAggregateCacheTime(); - continuousPageFetchOptions = pRequest.continuousPageFetchOptions(); - idSetPaginationOptions = pRequest.idSetPaginationOptions(); - topNProbeParentThreshold = pRequest.topNProbeParentThreshold(); - if (pRequest.getExtensions() != null) { - this.extensions.putAll(pRequest.getExtensions()); - } - facetRequests = pRequest.getFacetRequests(); - } - @Override public String getTypeName() { return type; diff --git a/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/PortableSQLDataService.java b/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/PortableSQLDataService.java index 58305685..e27bc6de 100644 --- a/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/PortableSQLDataService.java +++ b/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/PortableSQLDataService.java @@ -183,7 +183,7 @@ private void attachDynamicAggregations( } io.teaql.core.internal.TempRequest request = - dynamicAggregateRequest(aggregateRequest, partitionProperty, intent, parentRequest); + dynamicAggregateRequest(aggregateRequest, partitionProperty, intent, parentRequest, attribute); request.groupBy(partitionProperty); request.appendSearchCriteria( request.createBasicSearchCriteria( @@ -220,7 +220,7 @@ private void attachDynamicAggregations( } private SqlDiagnosticRequest dynamicAggregateRequest(SearchRequest aggregateRequest, - String partitionProperty, SqlIntentRedactions intent, SearchRequest parentRequest) { + String partitionProperty, SqlIntentRedactions intent, SearchRequest parentRequest, SimpleAggregation attribute) { EntityDescriptor aggregateDescriptor = metadata.resolveEntityDescriptor(aggregateRequest.getTypeName()); PropertyDescriptor partition = findProperty(aggregateDescriptor, partitionProperty); if (partition instanceof Relation relation && shouldHandle(aggregateDescriptor, relation)) { @@ -228,14 +228,14 @@ private SqlDiagnosticRequest dynamicAggregateRequest(SearchRequest aggregateR EntityDescriptor parentDescriptor = metadata.resolveEntityDescriptor(parentRequest.getTypeName()); while (reverse != null && parentDescriptor != null) { if (reverse.getOwner() == parentDescriptor) { - return SqlDiagnosticRequest.forRelation( - aggregateRequest, intent, parentRequest, reverse.getName()); + return SqlDiagnosticRequest.forAggregation( + aggregateRequest, intent, parentRequest, attribute, reverse.getName()); } parentDescriptor = parentDescriptor.getParent(); } } // Arbitrary partitions still inherit their request origin, but cannot claim a model edge. - return SqlDiagnosticRequest.forDerived(aggregateRequest, intent, parentRequest); + return SqlDiagnosticRequest.forAggregation(aggregateRequest, intent, parentRequest, attribute, null); } private Entity parentByAggregationKey(Map parentsById, Object parentId) { diff --git a/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/PortableSQLRepository.java b/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/PortableSQLRepository.java index b38c95c6..dc4da6c9 100644 --- a/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/PortableSQLRepository.java +++ b/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/PortableSQLRepository.java @@ -35,6 +35,8 @@ import io.teaql.core.Expression; import io.teaql.core.OrderBy; import io.teaql.core.OrderBys; +import io.teaql.core.QueryOperation; +import io.teaql.core.TeaQLConstants; import io.teaql.core.TeaQLRuntimeException; import io.teaql.core.SearchCriteria; @@ -564,7 +566,7 @@ private record ContinuousPageExecution( boolean optimized) {} private record IdSetExecution( - SearchRequest request, long[] pageIds, boolean optimized) {} + SearchRequest request, long[] pageIds, boolean optimized, Long exactCount) {} @SuppressWarnings("unchecked") private IdSetExecution prepareIdSetPage(UserContext context, SearchRequest original, @@ -607,7 +609,10 @@ private IdSetExecution prepareIdSetPage(UserContext context, SearchRequest } String key = idSetQueryKey(context, working, options, idSql, idParams); // Capture current bindings even when retained IDs avoid executing the discovery query. - PositionalSQL idStatement = withQueryIntent(toPositional(idSql, idParams), intent, idRequest); + var helperScope = io.teaql.core.QueryRequestScope.capture(original).deriveHelper( + idRequest.getTypeName(), "collect " + idRequest.getTypeName() + " IDs for pagination"); + var helper = SqlDiagnosticRequest.forOwnedScope(idRequest, intent, original, helperScope, null); + PositionalSQL idStatement = withQueryIntent(toPositional(idSql, idParams), intent, helper); IdSetStore store = idSetStore(context); RetainedIdSet retained; try { @@ -666,7 +671,7 @@ private IdSetExecution prepareIdSetPage(UserContext context, SearchRequest if (pageIds.length > 0) { page.appendSearchCriteria(page.createBasicSearchCriteria("id", Operator.IN, pageIds)); } - return new IdSetExecution<>((SearchRequest) page, pageIds, true); + return new IdSetExecution<>((SearchRequest) page, pageIds, true, (long) all.length); } private IdSetExecution idSetFallback(UserContext context, SearchRequest request, @@ -674,7 +679,7 @@ private IdSetExecution idSetFallback(UserContext context, SearchRequest re context.putAttribute(ID_SET_PLAN, plan); context.putAttribute(ID_SET_COUNT, count); context.putAttribute(ID_SET_COUNT_ACCURACY, accuracy); - return new IdSetExecution<>(request, new long[0], false); + return new IdSetExecution<>(request, new long[0], false, null); } private IdSetStore idSetStore(UserContext context) { @@ -716,6 +721,21 @@ SmartList loadInternal(UserContext userContext, SearchRequest request, return loadInternal(userContext, intent == null ? request : SqlDiagnosticRequest.forExecution(request, intent)); } + private SmartList attachExactIdSetCount(UserContext context, SearchRequest request, + IdSetExecution execution, SmartList rows) { + QueryOperation operation = request.pageCountOperation(); + if (operation == null || execution.exactCount() == null) return rows; + String alias = operation.alias() == null ? TeaQLConstants.ROOT_LIST_PARAMETER_NAME : operation.alias(); + if (rows.isSharedEmpty()) rows = new SmartList<>(); + AggregationItem item = new AggregationItem(); + item.setValues(Map.of(new SimpleNamedExpression(alias), execution.exactCount())); + AggregationResult count = new AggregationResult(); + count.setData(List.of(item)); + rows.addAggregationResult(context, count); + rows.__internalSetPageCountAlias(alias); + return rows; + } + private SmartList loadWithIntent(UserContext userContext, SearchRequest request, io.teaql.core.SqlIntentRedactions intent) { // Inspect the current typed request, including future child predicates, before @@ -723,7 +743,7 @@ private SmartList loadWithIntent(UserContext userContext, SearchRequest re SqlLikeIntent.capture(userContext, request, this, intent); IdSetExecution idSetExecution = prepareIdSetPage(userContext, request, intent); if (idSetExecution.optimized() && idSetExecution.pageIds().length == 0) { - return SmartList.empty(request.returnType()); + return attachExactIdSetCount(userContext, request, idSetExecution, SmartList.empty(request.returnType())); } SearchRequest requestedPage = idSetExecution.request(); QueryShape shape = idSetExecution.optimized() ? null : simpleQueryShape(userContext, request); @@ -776,7 +796,7 @@ private SmartList loadWithIntent(UserContext userContext, SearchRequest re List entities = database.query(userContext, psql.sql, psql.args, mapper, psql.logBindings); if (entities.isEmpty() && ObjectUtil.isEmpty(request.getFacetRequests())) { registerContinuousPage(userContext, request, pageExecution, List.of()); - return SmartList.empty(request.returnType()); + return attachExactIdSetCount(userContext, request, idSetExecution, SmartList.empty(request.returnType())); } smartList = SmartList.takeOwnership(entities); } @@ -784,7 +804,7 @@ private SmartList loadWithIntent(UserContext userContext, SearchRequest re List> rows = database.query(userContext, psql.sql, psql.args, psql.logBindings); if (rows.isEmpty() && ObjectUtil.isEmpty(request.getFacetRequests())) { registerContinuousPage(userContext, request, pageExecution, List.of()); - return SmartList.empty(request.returnType()); + return attachExactIdSetCount(userContext, request, idSetExecution, SmartList.empty(request.returnType())); } smartList = new SmartList<>(rows.size()); MapProjection[] projections = new MapProjection[16]; @@ -807,44 +827,82 @@ private SmartList loadWithIntent(UserContext userContext, SearchRequest re positions.getOrDefault(entity.getId(), Integer.MAX_VALUE))); } + // Bind exact count to this result before nested/facet queries overwrite + // context-level optimization diagnostics. Never infer another query's total. + smartList = attachExactIdSetCount(userContext, request, idSetExecution, smartList); java.util.List facetRequests = request.getFacetRequests(); if (facetRequests != null && !facetRequests.isEmpty()) { io.teaql.core.sql.SqlAstCompiler compiler = new io.teaql.core.sql.SqlAstCompiler(); for (io.teaql.core.FacetRequest facetRequest : facetRequests) { + io.teaql.core.SearchRequest relationReq = facetRequest.getRequest(); + java.util.List countAliases = relationReq == null ? java.util.List.of() : relationReq.getAggregations().getAggregates() + .stream().filter(expression -> expression.getExpression() instanceof io.teaql.core.AggrExpression aggregate + && aggregate.getOperator() == io.teaql.core.AggrFunction.COUNT) + .map(io.teaql.core.SimpleNamedExpression::name).toList(); + if (countAliases.isEmpty()) countAliases = java.util.List.of("count"); + var operation = facetRequest.getOperation() == null + ? io.teaql.core.QueryOperation.framework(io.teaql.core.QueryOperation.Kind.FACET, + facetRequest.getFacetName(), facetRequest.getRelationName()) : facetRequest.getOperation(); + var facetScope = io.teaql.core.QueryRequestScope.capture(request).deriveOperation(request.getTypeName(), operation); io.teaql.core.internal.TempRequest tr = - SqlDiagnosticRequest.forDerived(request, intent, request); + SqlDiagnosticRequest.forOwnedScope(request, intent, request, + facetScope.deriveHelper(request.getTypeName(), "aggregate " + facetRequest.getRelationName()), null); tr.setAggregations(new io.teaql.core.Aggregations()); tr.groupBy(facetRequest.getRelationName()); - tr.count("count"); + for (String alias : countAliases) tr.count(alias); Map facetParams = new io.teaql.core.sql.SqlParameters(); java.util.List facetTables = compiler.collectAggregationTables(this.sqlMetadata, this, userContext, tr); String facetSql = compiler.buildAggregationSQL(this.sqlMetadata, this, userContext, tr, facetParams, facetTables); if (!io.teaql.core.utils.ObjectUtil.isEmpty(facetSql)) { var facetIntent = intent == null ? null : intent.copy(); - PositionalSQL psqlFacet = withQueryIntent(toPositional(facetSql, facetParams), facetIntent, request); + PositionalSQL psqlFacet = withQueryIntent(toPositional(facetSql, facetParams), facetIntent, tr); List> facetRows = database.query(userContext, psqlFacet.sql, psqlFacet.args, psqlFacet.logBindings); SmartList facetEntities = new SmartList<>(); - io.teaql.core.SearchRequest relationReq = facetRequest.getRequest(); if (relationReq != null) { String relationType = relationReq.getTypeName(); PortableSQLRepository relationRepo = resolver.resolve(relationType); if (relationRepo != null) { List relIds = new ArrayList<>(); - Map idToCount = new HashMap<>(); + Map> idToCounts = new HashMap<>(); for (Map facetRow : facetRows) { Object relId = findFacetRelationValue( facetRow, facetRequest.getRelationName()); - Object countVal = findColumnValue(facetRow, "count"); if (relId != null) { relIds.add(relId); - idToCount.put(io.teaql.core.utils.Convert.convert(Long.class, relId), countVal); + Map values = new HashMap<>(); + for (String alias : countAliases) values.put(alias, findColumnValue(facetRow, alias)); + idToCounts.put(io.teaql.core.utils.Convert.convert(Long.class, relId), values); } } io.teaql.core.internal.TempRequest fetchRelReq = - SqlDiagnosticRequest.forRelation( - relationReq, facetIntent, request, facetRequest.getRelationName()); + SqlDiagnosticRequest.forOwnedScope(relationReq, facetIntent, request, + facetScope.deriveChild(relationType, relationReq.requestScope() == null + ? relationReq.comment() : relationReq.requestScope().comment()), facetRequest.getRelationName()); + // The facet's count expressions describe source membership, + // not an aggregate-only query over the target entity table. + fetchRelReq.setAggregations(new io.teaql.core.Aggregations()); + for (Object entry : fetchRelReq.getDynamicAggregateAttributes()) { + io.teaql.core.SimpleAggregation aggregate = (io.teaql.core.SimpleAggregation) entry; + io.teaql.core.SearchRequest child = aggregate.getAggregateRequest(); + if (child.getTypeName().equals(request.getTypeName())) { + // Keep the complete policy/search membership, independent + // of page offset/size and limited materialized parent rows. + var membership = new io.teaql.core.internal.TempRequest(request); + membership.setSlice(null); + membership.setAggregations(new io.teaql.core.Aggregations()); + membership.getProjections().clear(); + membership.selectProperty(BaseEntity.ID_PROPERTY); + membership.setOrderBy(new io.teaql.core.OrderBys()); + membership.enhanceRelations().clear(); + membership.enhanceChildren().clear(); + membership.getFacetRequests().clear(); + membership.getDynamicAggregateAttributes().clear(); + child.appendSearchCriteria(new io.teaql.core.SubQuerySearchCriteria( + BaseEntity.ID_PROPERTY, membership, BaseEntity.ID_PROPERTY)); + } + } if (facetRequest.isMergeCriteria()) { // The count query already applied the source's // filters. Its FK membership is the only valid @@ -853,17 +911,16 @@ private SmartList loadWithIntent(UserContext userContext, SearchRequest re fetchRelReq.appendSearchCriteria(fetchRelReq.createBasicSearchCriteria( BaseEntity.ID_PROPERTY, io.teaql.core.criteria.Operator.IN, relIds)); } - SmartList loadedRels = relationRepo.loadInternal(userContext, fetchRelReq, facetIntent); - java.util.List countAliases = relationReq.getAggregations().getAggregates() - .stream().map(io.teaql.core.SimpleNamedExpression::name).toList(); - if (countAliases.isEmpty()) countAliases = java.util.List.of("count"); + // Use the normal nested execution boundary so selected relations + // and typed reverse aggregates are hydrated, not silently dropped. + SmartList loadedRels = userContext.internalExecuteForList(fetchRelReq); for (Object obj : loadedRels) { io.teaql.core.Entity rel = (io.teaql.core.Entity) obj; - Object cnt = idToCount.get(rel.getId()); - int countInt = toIntOrZero(cnt); + Map counts = idToCounts.get(rel.getId()); if (rel instanceof io.teaql.core.BaseEntity) { for (String alias : countAliases) { - ((io.teaql.core.BaseEntity) rel).addDynamicProperty(alias, countInt); + ((io.teaql.core.BaseEntity) rel).addDynamicProperty(alias, + toIntOrZero(counts == null ? null : counts.get(alias))); } } facetEntities.add(rel); diff --git a/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/SqlDiagnosticRequest.java b/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/SqlDiagnosticRequest.java index 630e1398..c6d3684a 100644 --- a/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/SqlDiagnosticRequest.java +++ b/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/SqlDiagnosticRequest.java @@ -12,6 +12,7 @@ final class SqlDiagnosticRequest extends TempRequest { private final transient boolean executionScope; private final transient SearchRequest original; private final transient QueryIntent rootIntent; + private final transient io.teaql.core.QueryRequestScope requestScope; private final transient java.util.List traceSource; private final transient java.util.function.Consumer statementObserver; @@ -40,6 +41,8 @@ private SqlDiagnosticRequest(SearchRequest request, SqlIntentRedactions sourc this.original = request; // TempRequest's relation-oriented copy omits these root-query semantics. this.rootIntent = java.util.Objects.requireNonNull(rootIntent, "rootIntent"); + this.requestScope = request.requestScope() == null + ? io.teaql.core.QueryRequestScope.root(request.getTypeName(), rootIntent) : request.requestScope(); this.comment = rootIntent.comment(); this.purpose = rootIntent.purpose(); this.searchForText = request.getSearchForText(); @@ -74,7 +77,7 @@ static SqlDiagnosticRequest collecting(SearchRequest request, SqlIntentRedact } private static java.util.function.Consumer observer(SearchRequest request) { - return request instanceof SqlDiagnosticRequest scoped ? scoped.statementObserver : null; + return request.statementObserver(); } static io.teaql.core.SqlExecutionTrace statementTrace(SearchRequest request) { @@ -82,13 +85,19 @@ static io.teaql.core.SqlExecutionTrace statementTrace(SearchRequest request) } @Override public QueryIntent inheritedQueryIntent() { return rootIntent; } + @Override public java.util.function.Consumer statementObserver() { return statementObserver; } + @Override public SqlIntentRedactions sqlIntentSource() { return source == null ? null : source.copy(); } + @Override public io.teaql.core.QueryRequestScope requestScope() { return requestScope; } @Override public java.util.List sqlTraceSource() { return traceSource; } /** Derived work keeps its parent's origin even when it does not traverse a model relation. */ static SqlDiagnosticRequest forDerived(SearchRequest child, SqlIntentRedactions source, SearchRequest parent) { QueryIntent intent = parentIntent(parent); - return new SqlDiagnosticRequest(child, source, intent, false, parentTrace(parent, intent), observer(parent)); + var parentScope = io.teaql.core.QueryRequestScope.capture(parent); + var scope = parentScope.deriveHelper(child.getTypeName(), "execute query helper " + child.getTypeName()); + return new SqlDiagnosticRequest(new io.teaql.core.QueryRequestSnapshot(child, scope), + source, intent, false, parentTrace(parent, intent), observer(parent)); } static SqlDiagnosticRequest forRelation(SearchRequest child, SqlIntentRedactions source, @@ -97,7 +106,39 @@ static SqlDiagnosticRequest forRelation(SearchRequest child, SqlIntentRedacti var trace = new java.util.ArrayList<>(parentTrace(parent, intent)); trace.add(new io.teaql.core.TraceNode(io.teaql.core.TraceKind.RELATION, relationName, parent.getTypeName() + "." + relationName)); - return new SqlDiagnosticRequest(child, source, intent, false, trace, observer(parent)); + var operation = parent.queryOperations().getOrDefault(relationName, + io.teaql.core.QueryOperation.framework(io.teaql.core.QueryOperation.Kind.RELATION_SELECT, null, relationName)); + var operationScope = io.teaql.core.QueryRequestScope.capture(parent).deriveOperation(parent.getTypeName(), operation); + String comment = child.requestScope() == null ? child.comment() : child.requestScope().comment(); + var scope = operationScope.deriveChild(child.getTypeName(), comment); + return new SqlDiagnosticRequest(new io.teaql.core.QueryRequestSnapshot(child, scope), source, intent, false, trace, observer(parent)); + } + + static SqlDiagnosticRequest forOwnedScope(SearchRequest child, SqlIntentRedactions source, + SearchRequest parent, io.teaql.core.QueryRequestScope scope, String relationName) { + QueryIntent intent = parentIntent(parent); + var trace = new java.util.ArrayList<>(parentTrace(parent, intent)); + if (relationName != null) trace.add(new io.teaql.core.TraceNode(io.teaql.core.TraceKind.RELATION, + relationName, parent.getTypeName() + "." + relationName)); + return new SqlDiagnosticRequest(new io.teaql.core.QueryRequestSnapshot(child, scope), source, intent, false, trace, observer(parent)); + } + + static SqlDiagnosticRequest forAggregation(SearchRequest child, SqlIntentRedactions source, + SearchRequest parent, io.teaql.core.SimpleAggregation aggregate, String relationName) { + var parentScope = io.teaql.core.QueryRequestScope.capture(parent); + var facet = parentScope.parentRequest(); + io.teaql.core.QueryRequestScope operationScope; + if (facet != null && facet.operation() != null && facet.operation().kind() == io.teaql.core.QueryOperation.Kind.FACET) { + operationScope = facet.deriveHelper(child.getTypeName(), "aggregate " + facet.operation().relation()); + } else { + var operation = aggregate.getOperation() == null + ? io.teaql.core.QueryOperation.framework(io.teaql.core.QueryOperation.Kind.AGGREGATE, aggregate.getName(), relationName) + : aggregate.getOperation(); + operationScope = parentScope.deriveOperation(child.getTypeName(), operation); + } + String comment = child.requestScope() == null ? child.comment() : child.requestScope().comment(); + var scope = comment == null ? operationScope : operationScope.deriveChild(child.getTypeName(), comment); + return forOwnedScope(child, source, parent, scope, relationName); } private static QueryIntent parentIntent(SearchRequest parent) { @@ -120,6 +161,7 @@ static SqlIntentRedactions source(UserContext context, SearchRequest request) if (!context.isQueryExecutionLoggingEnabled() && observer(request) == null) return null; if (request instanceof SqlDiagnosticRequest scoped && scoped.source != null) return scoped.executionScope ? scoped.source : scoped.source.copy(); + if (request.sqlIntentSource() != null) return request.sqlIntentSource(); return new SqlIntentRedactions(); } } diff --git a/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/SqlLikeIntent.java b/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/SqlLikeIntent.java index 3043aeac..6680fc38 100644 --- a/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/SqlLikeIntent.java +++ b/teaql-sql-portable/src/main/java/io/teaql/core/sql/portable/SqlLikeIntent.java @@ -23,6 +23,7 @@ static void capture(UserContext context, SearchRequest request, private void request(SearchRequest request, PortableSQLRepository repository) { if (request == null || repository == null || !visited.add(request)) return; + request(request.diagnosticSource(), repository); expression(request.getSearchCriteria(), repository); if (request.getProjections() != null) for (var projection : request.getProjections()) expression(projection.getExpression(), repository); diff --git a/teaql-sql-portable/src/test/java/io/teaql/core/sql/portable/PortableSQLDatabaseTest.java b/teaql-sql-portable/src/test/java/io/teaql/core/sql/portable/PortableSQLDatabaseTest.java index 6cc8408e..02d6e7f6 100644 --- a/teaql-sql-portable/src/test/java/io/teaql/core/sql/portable/PortableSQLDatabaseTest.java +++ b/teaql-sql-portable/src/test/java/io/teaql/core/sql/portable/PortableSQLDatabaseTest.java @@ -1358,8 +1358,9 @@ public void testIdSetPaginationBuildsOnceJumpsAndReturnsExactCount() { TaskRequest jumpedRequest = new TaskRequest().filterByStatus("ID-SET-PAGE"); jumpedRequest.addOrderByDescending("id"); jumpedRequest.optimizePaginationWithIdSet("id-set-page", 60, 100); + jumpedRequest.offset(2, 2).count(); SmartList jumped = jumpedRequest.comment("jump to retained ID page") - .purpose("verify ID set pagination").executeForPage(context, 2, 2); + .purpose("verify ID set pagination").executeForList(context); assertEquals(2, jumped.size()); assertEquals(5, jumped.getTotalCount()); @@ -1373,13 +1374,28 @@ public void testIdSetPaginationBuildsOnceJumpsAndReturnsExactCount() { TaskRequest firstRequest = new TaskRequest().filterByStatus("ID-SET-PAGE"); firstRequest.addOrderByDescending("id"); firstRequest.optimizePaginationWithIdSet("id-set-page", 60, 100); + firstRequest.offset(0, 2).count(); SmartList first = firstRequest.comment("load retained first page") - .purpose("verify ID set cache hit").executeForPage(context, 0, 2); + .purpose("verify ID set cache hit").executeForList(context); assertEquals(5, first.getTotalCount()); assertEquals("ID_SET_HIT", context.getAttribute(PortableSQLRepository.ID_SET_PLAN)); assertEquals(1, sqliteDb.queryTrace().size()); assertTrue(first.get(0).getId() > first.get(1).getId()); assertTrue(first.get(1).getId() > jumped.get(0).getId()); + + sqliteDb.clearQueryTrace(); + TaskRequest beyondRequest = new TaskRequest().filterByStatus("ID-SET-PAGE"); + beyondRequest.addOrderByDescending("id"); + beyondRequest.optimizePaginationWithIdSet("id-set-page", 60, 100); + beyondRequest.offset(10, 2).count("retainedTotal"); + SmartList beyond = beyondRequest.comment("load empty retained page") + .purpose("preserve exact total after the last page").executeForList(context); + assertTrue(beyond.isEmpty()); + assertEquals(5, beyond.getTotalCount()); + assertEquals(5L, ((Number) beyond.aggregationNumberProperties().get("retainedTotal")).longValue()); + assertTrue(sqliteDb.queryTrace().isEmpty()); + assertEquals(5, first.getTotalCount()); + assertEquals(5, jumped.getTotalCount()); } @Test @@ -1404,8 +1420,9 @@ public void testIdSetPaginationEmptyOverflowStoreFailureAndUnsupportedShape() { TaskRequest overflow = new TaskRequest().filterByStatus("ID-SET-OVERFLOW"); overflow.addOrderByDescending("id"); overflow.optimizePaginationWithIdSet("overflow", 60, 3); + overflow.offset(0, 2).count(); SmartList overflowPage = overflow.comment("overflow fallback") - .purpose("preserve ordinary page semantics").executeForPage(context, 0, 2); + .purpose("preserve ordinary page semantics").executeForList(context); assertEquals(2, overflowPage.size()); assertEquals(5, overflowPage.getTotalCount()); assertEquals("ID_SET_FALLBACK_LIMIT_EXCEEDED", diff --git a/teaql-sql-portable/src/test/java/io/teaql/core/sql/portable/SqlDiagnosticRequestTest.java b/teaql-sql-portable/src/test/java/io/teaql/core/sql/portable/SqlDiagnosticRequestTest.java index 53f76011..ec63ca0a 100644 --- a/teaql-sql-portable/src/test/java/io/teaql/core/sql/portable/SqlDiagnosticRequestTest.java +++ b/teaql-sql-portable/src/test/java/io/teaql/core/sql/portable/SqlDiagnosticRequestTest.java @@ -7,6 +7,35 @@ /** #202: derived request factories inherit their explicit parent, never ambient Context. */ public class SqlDiagnosticRequestTest { + @Test public void nativeOperationAndNestedCommentAreIndependentOfThePhysicalRoute() { + var parent = new Request("School", "load schools", "render school list"); + var child = new Request("SchoolType", "load visible types", null); + parent.enhanceRelation("schoolType", child, QueryOperation.nativeMethod( + QueryOperation.Kind.RELATION_SELECT, "selectSchoolTypeWith", null, "schoolType")); + var execution = SqlDiagnosticRequest.forExecution(parent, null); + var derived = SqlDiagnosticRequest.forRelation(child, null, execution, "schoolType"); + assertSame(execution.requestScope(), derived.requestScope().parentRequest().parentRequest()); + assertEquals(List.of("render school list", "load schools", "selectSchoolTypeWith(...)", "load visible types"), + derived.requestScope().traceChain().stream().map(TraceNode::getComment).toList()); + assertEquals(1, derived.sqlTraceSource().stream().filter(n -> n.getKind() == TraceKind.RELATION).count()); + assertEquals("load schools", derived.comment()); + var metadata = new ExecutionMetadata(); metadata.setBackend("sqlite"); + var trace = SqlExecutionTrace.query(derived); trace.applyTo(metadata); trace.recordStatement(metadata); + assertEquals(derived.requestScope().traceChain(), metadata.getRequestTraceChain()); + assertEquals("load schools", metadata.getComment()); + assertEquals("render school list", metadata.getPurpose()); + } + + @Test public void relationDefaultIsLocalAndExplicitBlankFailsBeforeSql() { + var parent = new Request("School", "load schools", "render school list"); + var child = new Request("SchoolType", null, null); + assertEquals("load SchoolType", SqlDiagnosticRequest.forRelation(child, null, parent, "schoolType") + .requestScope().comment()); + assertNull(child.comment()); assertNull(child.purpose()); + assertThrows(RequestIntentException.class, () -> SqlDiagnosticRequest.forRelation( + new Request("SchoolType", " \t ", null), null, parent, "schoolType")); + } + private static class Request extends BaseRequest { private final String type; Request(String type, String comment, String purpose) { diff --git a/teaql-sqlite/pom.xml b/teaql-sqlite/pom.xml index 3670630b..54a85105 100644 --- a/teaql-sqlite/pom.xml +++ b/teaql-sqlite/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-sqlite teaql-sqlite diff --git a/teaql-sqlite/src/test/java/io/teaql/sqlite/DerivedQueryTraceSqliteTest.java b/teaql-sqlite/src/test/java/io/teaql/sqlite/DerivedQueryTraceSqliteTest.java index a5d63dca..060acff5 100644 --- a/teaql-sqlite/src/test/java/io/teaql/sqlite/DerivedQueryTraceSqliteTest.java +++ b/teaql-sqlite/src/test/java/io/teaql/sqlite/DerivedQueryTraceSqliteTest.java @@ -25,14 +25,336 @@ public class DerivedQueryTraceSqliteTest { private static final String PRIVATE_NAME = "PRIVATE-DOCUMENT-NAME"; + private static List businessComments(ExecutionMetadata entry) { + return entry.getRequestTraceChain().stream().filter(node -> node.getKind() == TraceKind.COMMENT) + .map(TraceNode::getComment).toList(); + } + + @Test public void selectedRelationSqlCarriesNativeOperationAndExplicitOrDefaultComment() throws Exception { + for (boolean logging : List.of(false, true)) { + for (String childComment : new String[]{null, "load visible lines"}) { + var fixture = new Fixture(logging); + fixture.receivedChains.clear(); + var child = fixture.lines().intent(childComment, null); + var root = fixture.documents().where("id", Operator.EQUAL, 100L) + .intent("load documents", "render document details"); + root.enhanceRelation("lines", child, QueryOperation.nativeMethod( + QueryOperation.Kind.RELATION_SELECT, "selectLinesWith", null, "lines")); + var rows = fixture.context.getRuntime().executeForList(fixture.context, root); + assertEquals(1, rows.size()); + assertEquals(2, ((SmartList) rows.get(0).getProperty("lines")).size()); + assertEquals(2, fixture.receivedChains.size()); + assertEquals(List.of("load documents", "selectLinesWith(...)", childComment == null ? "load TraceLine" : childComment), + fixture.receivedChains.get(1).stream().filter(node -> node.getKind() == TraceKind.COMMENT) + .map(TraceNode::getComment).toList()); + assertEquals(logging ? 2 : 0, fixture.sql.size()); + if (logging) { + assertEquals(List.of("load documents"), businessComments(fixture.sql.get(0))); + assertEquals(List.of("load documents", "selectLinesWith(...)", childComment == null ? "load TraceLine" : childComment), + businessComments(fixture.sql.get(1))); + assertEquals("load documents", fixture.sql.get(1).getComment()); + assertEquals("render document details", fixture.sql.get(1).getPurpose()); + } + assertEquals(childComment, child.comment()); assertNull(child.requestScope()); + assertTrue(fixture.context.getTraceChain().isEmpty()); + System.out.println("RTC_COM_02_PASS logging=" + logging + " explicit=" + (childComment != null)); + } + } + } + + @Test public void explicitlyBlankRelationCommentRejectsBeforeChildProviderWithLoggingOnAndOff() throws Exception { + for (boolean logging : List.of(false, true)) { + for (String blank : List.of("", " \t", "\u0085")) { + var fixture = new Fixture(logging); + fixture.receivedChains.clear(); + var child = fixture.lines().intent(blank, null); + var root = fixture.documents().where("id", Operator.EQUAL, 100L) + .intent("load documents", "validate nested input"); + root.enhanceRelation("lines", child, QueryOperation.nativeMethod( + QueryOperation.Kind.RELATION_SELECT, "selectLinesWith", null, "lines")); + var error = assertThrows(RequestIntentException.class, + () -> fixture.context.getRuntime().executeForList(fixture.context, root)); + assertEquals("comment", error.getField()); + assertTrue("Only a valid root may have entered the provider", fixture.receivedChains.size() <= 1); + assertTrue(fixture.receivedChains.stream().allMatch(chain -> + chain.stream().filter(node -> node.getKind() == TraceKind.COMMENT).count() == 1)); + assertEquals(blank, child.comment()); + System.out.println("RTC_COM_04_RELATION_PASS logging=" + logging + " invalid_child_provider=0"); + } + } + } + + @Test public void explicitlyBlankFacetValueCommentRejectsBeforeValueProviderWithLoggingOnAndOff() throws Exception { + for (boolean logging : List.of(false, true)) { + for (String blank : List.of("", " \t", "\u0085")) { + var fixture = new Fixture(logging); + fixture.receivedChains.clear(); + var child = fixture.documents().intent(blank, null); + var root = fixture.lines().intent("load lines", "validate facet input"); + root.addFacet("documents", "document", child, true, QueryOperation.nativeMethod( + QueryOperation.Kind.FACET, "facetByDocumentAs", "documents", "document")); + var error = assertThrows(RequestIntentException.class, + () -> fixture.context.getRuntime().executeForList(fixture.context, root)); + assertEquals("comment", error.getField()); + assertTrue(fixture.receivedChains.stream().noneMatch(chain -> + chain.get(chain.size() - 1).getName().equals("TraceDocument"))); + assertEquals(blank, child.comment()); + System.out.println("RTC_COM_04_FACET_PASS logging=" + logging + " invalid_value_provider=0"); + } + } + } + + @Test public void namedPageCountKeepsAliasAndDoesNotMutateCallerOrInventRelation() throws Exception { + for (boolean logging : List.of(true, false)) { + var fixture = new Fixture(logging); + String alias = "totalDocuments'\\tail"; + var root = fixture.documents().intent("load documents", "render document page"); + root.count(alias); + root.setPageCountOperation(QueryOperation.nativeMethod(QueryOperation.Kind.NAMED_COUNT, "countAs", alias, null)); + root.offset(1, 1); + int originalSize = root.getSlice().getSize(); + var rows = fixture.context.getRuntime().executeForList(fixture.context, root); + assertEquals(1, rows.size()); assertEquals(2, rows.getTotalCount()); + assertEquals(2, ((Number) rows.aggregationNumberProperties().get(alias)).intValue()); + assertEquals(originalSize, root.getSlice().getSize()); + assertEquals(1, root.getAggregations().getAggregates().size()); + var raw = fixture.returnedQueries.get(fixture.returnedQueries.size() - 1).statements(); + assertEquals(1, raw.size()); + assertEquals(List.of("load documents", "countAs(\"totalDocuments'\\\\tail\")"), businessComments(raw.get(0))); + assertFalse(raw.get(0).getTracePath().stream().anyMatch(node -> node.getKind() == TraceKind.RELATION)); + assertEquals(logging ? 2 : 0, fixture.sql.size()); + fixture.sql.clear(); fixture.returnedQueries.clear(); + var unnamed = fixture.documents().intent("load documents", "render document page"); + unnamed.offset(0, 1).count(); + var defaultRows = fixture.context.getRuntime().executeForList(fixture.context, unnamed); + assertEquals(2, defaultRows.getTotalCount()); + var defaultCount = fixture.returnedQueries.get(fixture.returnedQueries.size() - 1).statements().get(0); + assertEquals(List.of("load documents", "count()"), businessComments(defaultCount)); + assertTrue(defaultCount.getParameterizedQuery().contains(TeaQLConstants.ROOT_LIST_PARAMETER_NAME)); + assertFalse(businessComments(defaultCount).toString().contains(TeaQLConstants.ROOT_LIST_PARAMETER_NAME)); + assertFalse(defaultCount.getTracePath().stream().anyMatch(node -> node.getKind() == TraceKind.RELATION)); + assertEquals(logging ? 2 : 0, fixture.sql.size()); + System.out.println("RTC_COM_05_PASS logging=" + logging + " named=true unnamed=true"); + } + } + + @Test public void facetAggregateAndValueSqlShareNativeParentWithoutFakeOperationStatements() throws Exception { + var fixture = new Fixture(true); + var values = fixture.documents().intent("load document facets", null); + var root = fixture.lines().where("state", Operator.EQUAL, "OPEN") + .intent("load open lines", "render line facets"); + root.addFacet("documents", "document", values, true, + QueryOperation.nativeMethod(QueryOperation.Kind.FACET, "facetByDocumentAs", "documents", "document")); + var rows = fixture.context.getRuntime().executeForList(fixture.context, root); + assertEquals(2, rows.size()); assertEquals(2, rows.getFacet("documents").size()); + assertEquals(3, fixture.sql.size()); + assertEquals(List.of("load open lines", "facetByDocumentAs(\"documents\")", "aggregate document"), businessComments(fixture.sql.get(1))); + assertEquals(List.of("load open lines", "facetByDocumentAs(\"documents\")", "load document facets"), businessComments(fixture.sql.get(2))); + assertTrue(fixture.sql.stream().allMatch(entry -> "render line facets".equals(entry.getPurpose()))); + assertNull(values.purpose()); assertNull(values.requestScope()); + } + + @Test public void nestedSiblingFacetsRetainOperationParentsAndIndependentComments() throws Exception { + for (boolean logging : List.of(false, true)) { + var fixture = new Fixture(logging, JdbcSqlExecutor::new, true); + fixture.document.updateProperty("peer", fixture.document); + fixture.document.auditAs("seed nested facet relation").save(fixture.context); + var outer = fixture.lines().where("document", Operator.EQUAL, fixture.document) + .intent("load nested facets", "render facet branches"); + for (String alias : List.of("left", "right")) { + var nested = fixture.documents().intent("load " + alias + " peers", null); + nested.count("documentCount"); + var values = fixture.documents().intent("load " + alias + " documents", null); + values.count("lineCount"); + values.addFacet("peers", "peer", nested, false, + QueryOperation.nativeMethod(QueryOperation.Kind.FACET, "facetByPeerAs", "peers", "peer")); + outer.addFacet(alias, "document", values, false, + QueryOperation.nativeMethod(QueryOperation.Kind.FACET, "facetByDocumentAs", alias, "document")); + } + fixture.sql.clear(); fixture.returnedQueries.clear(); + var result = fixture.context.getRuntime().executeForList(fixture.context, outer); + assertEquals(2, result.size()); + // The completed root result already carries nested physical + // statements. Flattening every intermediate result double-counts. + var facts = fixture.returnedQueries.get(fixture.returnedQueries.size() - 1).statements(); + for (String alias : List.of("left", "right")) { + assertEquals(1, result.getFacet(alias).size()); + assertEquals(1, result.getFacet(alias).getFacet("peers").size()); + var prefix = List.of("load nested facets", "facetByDocumentAs(\"" + alias + "\")"); + var statements = facts.stream() + .filter(entry -> businessComments(entry).size() > 1 && businessComments(entry).subList(0,2).equals(prefix)).toList(); + assertEquals(4, statements.size()); + var chains = statements.stream().map(DerivedQueryTraceSqliteTest::businessComments).toList(); + assertTrue(chains.contains(List.of(prefix.get(0), prefix.get(1), "aggregate document"))); + assertTrue(chains.contains(List.of(prefix.get(0), prefix.get(1), "load " + alias + " documents"))); + for (String local : List.of("aggregate peer", "load " + alias + " peers")) + assertTrue(chains.contains(List.of(prefix.get(0), prefix.get(1), "load " + alias + " documents", "facetByPeerAs(\"peers\")", local))); + assertTrue(statements.stream().allMatch(entry -> entry.getPurpose().equals("render facet branches"))); + } + assertEquals(logging ? 9 : 0, fixture.sql.size()); + assertEquals(9, facts.size()); + System.out.println("RTC_COM_06_PASS logging=" + logging + " nested=true siblings=true"); + } + } + + @Test public void prefixesAndFacetAliasEscapingPreserveBindingsUnderPrivacyProjection() throws Exception { + for (boolean logging : List.of(false, true)) { + var fixture = new Fixture(logging); + for (boolean privateInput : List.of(false, true)) { + String alias = "choices\"\\\n" + (privateInput ? PRIVATE_NAME : ""); + String nativeComment = "facetByDocumentAs(\"choices\\\"\\\\\\n" + (privateInput ? PRIVATE_NAME : "") + "\")"; + String comment = privateInput ? "what: load " + PRIVATE_NAME : "what: load lines"; + String purpose = privateInput ? "why: inspect " + PRIVATE_NAME : "why: inspect lines"; + var child = fixture.documents().intent(privateInput ? "load " + PRIVATE_NAME + " facets" : "load document facets", null); + child.count("lineCount"); + if (privateInput) child.where("name", Operator.EQUAL, PRIVATE_NAME); + var query = fixture.lines().intent(comment, purpose); query.setSize(10); + var operation = QueryOperation.nativeMethod(QueryOperation.Kind.FACET, "facetByDocumentAs", alias, "document"); + assertEquals(nativeComment, operation.comment()); + query.addFacet(alias, "document", child, false, operation); + fixture.sql.clear(); fixture.returnedQueries.clear(); + var rows = fixture.context.getRuntime().executeForList(fixture.context, query); + assertEquals(3, rows.size()); + assertEquals(privateInput ? 1 : 2, rows.getFacet(alias).size()); + if (privateInput) assertEquals(100L, ((Entity) rows.getFacet(alias).get(0)).getId().longValue()); + assertEquals(comment, query.comment()); assertEquals(purpose, query.purpose()); + assertEquals(privateInput ? "load " + PRIVATE_NAME + " facets" : "load document facets", child.comment()); + var facts = fixture.returnedQueries.get(fixture.returnedQueries.size() - 1).statements(); + assertEquals(3, facts.size()); assertEquals(logging ? 3 : 0, fixture.sql.size()); + assertEquals(List.of(comment), businessComments(facts.get(0))); + // Provider metadata is internal and retains intent. The actual + // diagnostic sink (or the same explicit projection when off) + // is the privacy boundary, not mutation of the caller's tree. + var safeFacts = logging ? fixture.sql : facts.stream().map(entry -> LogPrivacy.sql(entry, false)).toList(); + String safeComment = privateInput ? "what: load [REDACTED]" : comment; + String safePurpose = privateInput ? "why: inspect [REDACTED]" : purpose; + assertEquals(List.of(safeComment), businessComments(safeFacts.get(0))); + for (String local : List.of("aggregate document", privateInput ? "load [REDACTED] facets" : "load document facets")) + assertTrue(safeFacts.stream().map(DerivedQueryTraceSqliteTest::businessComments).anyMatch(chain -> chain.equals( + List.of(safeComment, nativeComment.replace(PRIVATE_NAME, "[REDACTED]"), local)))); + assertTrue(safeFacts.stream().allMatch(entry -> safePurpose.equals(entry.getPurpose()))); + if (privateInput) assertTrue(safeFacts.stream().allMatch(entry -> !entry.getRequestTraceChain().toString().contains(PRIVATE_NAME))); + } + System.out.println("RTC_COM_09_12_PASS logging=" + logging + " plain=true private=true escaped=true bindings=true"); + } + } + + @Test public void compoundAggregatesEmitOnePhysicalStatementWithLoggingOnAndOff() throws Exception { + for (boolean logging : List.of(false, true)) { + var fixture = new Fixture(logging); + var query = fixture.lines().intent("calculate line totals", "render totals"); + query.count("lineCount"); query.sum("documentTotal", "documentNumber"); + var result = (DefaultQueryResult) fixture.provider.query(fixture.context, new DefaultQueryRequest(query)); + var values = result.getAggregationResult().toList().get(0); + assertEquals(3L, ((Number) values.get("lineCount")).longValue()); + assertEquals(400L, ((Number) values.get("documentTotal")).longValue()); + assertEquals(1, result.statements().size()); + var entry = result.statements().get(0); + assertTrue(entry.getParameterizedQuery().toUpperCase(Locale.ROOT).contains("COUNT(")); + assertTrue(entry.getParameterizedQuery().toUpperCase(Locale.ROOT).contains("SUM(")); + assertEquals(List.of("calculate line totals"), businessComments(entry)); + assertEquals(logging ? 1 : 0, fixture.sql.size()); + System.out.println("RTC_COM_07_AGGREGATE_PASS logging=" + logging + " expressions=2 statements=1"); + } + } + + @Test public void idSetDiscoveryOwnsHelperAndRetainedPagesDoNotInventCountSql() throws Exception { + for (boolean logging : List.of(true, false)) { + var fixture = new Fixture(logging); + var root = fixture.documents().intent("load documents", "render document page"); + root.count("cachedTotal"); + root.setPageCountOperation(QueryOperation.nativeMethod(QueryOperation.Kind.NAMED_COUNT, "countAs", "cachedTotal", null)); + root.offset(1, 1); root.optimizePaginationWithIdSet("request-tree-documents", 60, 10); + fixture.returnedQueries.clear(); + var rows = fixture.context.getRuntime().executeForList(fixture.context, root); + assertEquals(2, rows.getTotalCount()); + assertEquals(200L, rows.get(0).getId().longValue()); + var facts = fixture.returnedQueries.stream().flatMap(result -> result.statements().stream()).toList(); + assertEquals(2, facts.size()); + assertEquals(List.of("load documents", "collect TraceDocument IDs for pagination"), businessComments(facts.get(0))); + assertEquals(List.of("load documents"), businessComments(facts.get(1))); + assertEquals(logging ? 2 : 0, fixture.sql.size()); + root.offset(0, 1); fixture.returnedQueries.clear(); fixture.sql.clear(); + root.offset(0, 1); + rows = fixture.context.getRuntime().executeForList(fixture.context, root); + assertEquals(2, rows.getTotalCount()); + assertEquals(100L, rows.get(0).getId().longValue()); + facts = fixture.returnedQueries.stream().flatMap(result -> result.statements().stream()).toList(); + assertEquals(1, facts.size()); + assertEquals(List.of("load documents"), businessComments(facts.get(0))); + assertFalse(facts.get(0).getTracePath().stream().anyMatch(node -> node.getKind() == TraceKind.RELATION)); + assertFalse(facts.get(0).getParameterizedQuery().toUpperCase(Locale.ROOT).contains("COUNT(")); + System.out.println("RTC_COM_07_CACHE_PASS logging=" + logging + " accuracy=EXACT hit_statements=1"); + } + } + + @Test public void nativeRelatedCountOwnsOneOperationNodeAndRetainsPrivacy() throws Exception { + var fixture = new Fixture(true); + var root = fixture.withOpenCount().where("name", Operator.EQUAL, PRIVATE_NAME) + .intent("load " + PRIVATE_NAME, "render counts"); + root.getDynamicAggregateAttributes().get(0).setOperation(QueryOperation.nativeMethod( + QueryOperation.Kind.RELATION_COUNT, "countLinesAs", "openLineCount", "lines")); + var rows = fixture.context.getRuntime().executeForList(fixture.context, root); + assertEquals(1, ((Number) rows.get(0).getDynamicProperty("openLineCount")).intValue()); + assertEquals(2, fixture.sql.size()); + assertEquals(List.of("load [REDACTED]", "countLinesAs(\"openLineCount\")"), businessComments(fixture.sql.get(1))); + assertFalse(fixture.sql.stream().flatMap(entry -> entry.getRequestTraceChain().stream()) + .anyMatch(node -> node.getComment().contains(PRIVATE_NAME))); + } + + @Test public void facetReverseSumKeepsExplicitCommentAndRejectsBlankBeforeAggregateSql() throws Exception { + for (boolean logging : List.of(true, false)) { + for (String comment : List.of("sum visible document numbers", "", " \t", "\u0085")) { + var fixture = new Fixture(logging); + var sum = fixture.lines().intent(comment, null); + var values = fixture.documents().intent("load document facets", null); + values.addRelationScalarAggregate("documentTotal", sum, "document", "documentNumber", AggrFunction.SUM, + QueryOperation.nativeMethod(QueryOperation.Kind.AGGREGATE, + "sumDocumentNumberOfLinesAs", "documentTotal", "lines")); + var root = fixture.lines().where("state", Operator.EQUAL, "OPEN") + .intent("load open lines", "render line facets"); + root.addFacet("documents", "document", values, true, + QueryOperation.nativeMethod(QueryOperation.Kind.FACET, "facetByDocumentAs", "documents", "document")); + fixture.returnedQueries.clear(); + if (!comment.equals("sum visible document numbers")) { + assertThrows(RequestIntentException.class, + () -> fixture.context.getRuntime().executeForList(fixture.context, root)); + assertTrue("invalid aggregate must not reach SQLite", fixture.returnedQueries.stream() + .flatMap(result -> result.statements().stream()) + .noneMatch(entry -> businessComments(entry).contains("sumDocumentNumberOfLinesAs(\"documentTotal\")") + || businessComments(entry).size() > 3)); + continue; + } + var rows = fixture.context.getRuntime().executeForList(fixture.context, root); + var facets = rows.getFacet("documents"); + assertEquals(2, facets.size()); + for (var value : facets) { + Entity document = (Entity) value; + assertEquals(document.getId().longValue(), + ((Number) document.getDynamicProperty("documentTotal")).longValue()); + } + // Parent results also embed their descendants' statement metadata. + // Count each execution object once, never deduplicate by comment text. + Set statements = Collections.newSetFromMap(new IdentityHashMap<>()); + fixture.returnedQueries.forEach(result -> statements.addAll(result.statements())); + assertEquals(4, statements.size()); + var expected = List.of("load open lines", "facetByDocumentAs(\"documents\")", + "aggregate document", comment); + assertEquals(1, statements.stream().filter(entry -> businessComments(entry).equals(expected)).count()); + assertEquals(logging ? 4 : 0, fixture.sql.size()); + assertEquals(comment, sum.comment()); assertNull(sum.requestScope()); + } + } + } + public abstract static class Row extends BaseEntity { private final Map values = new HashMap<>(); @Override public Object __internalGet(String field) { - return field.equals("name") || field.equals("state") || field.equals("document") || field.equals("lines") || field.equals("documentNumber") + return field.equals("name") || field.equals("state") || field.equals("document") || field.equals("lines") || field.equals("documentNumber") || field.equals("peer") || field.equals("peers") ? values.get(field) : super.__internalGet(field); } @Override public void __internalSet(String field, Object value) { - if (field.equals("name") || field.equals("state") || field.equals("document") || field.equals("lines") || field.equals("documentNumber")) + if (field.equals("name") || field.equals("state") || field.equals("document") || field.equals("lines") || field.equals("documentNumber") || field.equals("peer") || field.equals("peers")) values.put(field,value); else super.__internalSet(field,value); } @@ -60,6 +382,7 @@ Request intent(String comment, String purpose) { private static final class Fixture { final List sql = new CopyOnWriteArrayList<>(); final List returnedQueries = new CopyOnWriteArrayList<>(); + final List> receivedChains = new CopyOnWriteArrayList<>(); final SimpleEntityMetaFactory metadata = new SimpleEntityMetaFactory(); final DefaultUserContext context; final SqliteDataServiceExecutor provider; @@ -69,6 +392,9 @@ private static final class Fixture { this(logging, JdbcSqlExecutor::new); } Fixture(boolean logging, Function driverFactory) throws Exception { + this(logging, driverFactory, false); + } + Fixture(boolean logging, Function driverFactory, boolean nestedPeers) throws Exception { var source = new SQLiteDataSource(); source.setUrl("jdbc:sqlite:" + Files.createTempFile("teaql-derived-trace-", ".db")); var documents = descriptor(TraceDocument.class,TraceDocument::new); @@ -79,9 +405,15 @@ private static final class Fixture { documentNumber.setColumnType("BIGINT"); var relation = (GenericSQLRelation) lines.addObjectProperty(metadata,"document", "TraceDocument", "lines",TraceDocument.class); relation.setColumnType("BIGINT"); + if (nestedPeers) { + var peer = (GenericSQLRelation) documents.addObjectProperty(metadata, + "peer", "TraceDocument", "peers", TraceDocument.class); + peer.setColumnType("BIGINT"); + } var driver = driverFactory.apply(source); provider = new SqliteDataServiceExecutor("sqlite",driver,source) { @Override public QueryResult query(UserContext caller, QueryRequest request) { + receivedChains.add(request.businessTraceChain()); var result = super.query(caller, request); returnedQueries.add((DefaultQueryResult) result); return result; @@ -213,6 +545,14 @@ private void hold(String sql) { } @Test public void twoLiveSqliteQueriesOnOneContextKeepPhysicalIntentAndRelationsIsolated() throws Exception { + verifyTwoLiveSqliteQueries(false); + } + + @Test public void sharedTemplateConcurrentParentsKeepSeparateOwners() throws Exception { + verifyTwoLiveSqliteQueries(true); + } + + private void verifyTwoLiveSqliteQueries(boolean sharedTemplate) throws Exception { for (boolean logging : List.of(false, true)) { var driver = new AtomicReference(); var fixture = new Fixture(logging, source -> { @@ -229,7 +569,8 @@ private void hold(String sql) { .intent("load beta graph", "render beta graph"); alpha.setSize(1); beta.setSize(1); var alphaLines = fixture.lines(); alphaLines.setSize(10); - var betaLines = fixture.lines(); betaLines.setSize(10); + var betaLines = sharedTemplate ? alphaLines : fixture.lines(); betaLines.setSize(10); + var templateProjections = List.copyOf(alphaLines.getProjections()); alpha.enhanceRelation("lines", alphaLines); beta.enhanceRelation("lines", betaLines); var first = workers.submit(() -> fixture.context.getRuntime().executeForList(fixture.context, alpha)); var second = workers.submit(() -> fixture.context.getRuntime().executeForList(fixture.context, beta)); @@ -239,6 +580,8 @@ private void hold(String sql) { assertEquals(2, gate.roots.get()); assertTrue(fixture.sql.isEmpty()); assertTrue(fixture.returnedQueries.isEmpty()); assertEquals("live requests must not put their frames on Context", baseline, fixture.context.getTraceChain()); + assertNull(alphaLines.requestScope()); assertNull(alphaLines.comment()); assertNull(alphaLines.purpose()); + assertEquals(templateProjections, alphaLines.getProjections()); assertEquals(10, alphaLines.getSlice().getSize()); System.out.printf("LIVE_SQLITE_BARRIER logging=%s physicalRoots=2 unfinishedQueries=2 contextUnchanged=true logs=0%n", logging); gate.release.countDown(); var results = List.of(first.get(10, TimeUnit.SECONDS), second.get(10, TimeUnit.SECONDS)); @@ -279,6 +622,8 @@ private void hold(String sql) { } } assertEquals(baseline, fixture.context.getTraceChain()); + assertNull(alphaLines.requestScope()); assertNull(alphaLines.comment()); assertNull(alphaLines.purpose()); + assertEquals(templateProjections, alphaLines.getProjections()); assertEquals(10, alphaLines.getSlice().getSize()); System.out.printf("LIVE_SQLITE_RESULT logging=%s physicalStatements=4 inheritedRelations=2 safeLogs=%d contextUnchanged=true%n", logging, fixture.sql.size()); } finally { gate.release.countDown(); workers.shutdownNow(); @@ -532,4 +877,25 @@ private void verifyFilteredReferenceMembership(boolean nested, boolean logging, assertTrue(fixture.context.getTraceChain().isEmpty()); } } + + @Test public void derivedRelationCursorPreservesOperationAndLocalCommentWithLoggingOnAndOff() throws Exception { + for (boolean logging : List.of(false, true)) { + var fixture = new Fixture(logging); + var root = new DefaultQueryRequest(fixture.documents().intent("load documents", "render line stream")); + var child = root.derive(fixture.lines().where("document", Operator.EQUAL, 100L) + .intent("stream selected lines", null), QueryOperation.nativeMethod( + QueryOperation.Kind.RELATION_SELECT, "selectLinesWith", null, "lines")); + try (var cursor = fixture.provider.queryForCursor(fixture.context, child)) { + assertEquals(2, cursor.stream().toList().size()); + assertEquals(1, cursor.statements().size()); + var entry = cursor.statements().get(0); + assertEquals(List.of("load documents", "selectLinesWith(...)", "stream selected lines"), businessComments(entry)); + assertEquals("render line stream", entry.getPurpose()); + assertEquals("success", entry.getExecutionOutcome()); + assertFalse(entry.getTracePath().stream().anyMatch(node -> node.getKind() == TraceKind.RELATION)); + } + assertEquals(logging ? 1 : 0, fixture.sql.size()); + assertTrue(fixture.context.getTraceChain().isEmpty()); + } + } } diff --git a/teaql-tfp-endpoint/pom.xml b/teaql-tfp-endpoint/pom.xml index 4a5070ac..15c02fd9 100644 --- a/teaql-tfp-endpoint/pom.xml +++ b/teaql-tfp-endpoint/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-tfp-endpoint/src/main/java/io/teaql/tfp/TfpEndpointHandler.java b/teaql-tfp-endpoint/src/main/java/io/teaql/tfp/TfpEndpointHandler.java index 8bffb4d9..bcc3b7b5 100644 --- a/teaql-tfp-endpoint/src/main/java/io/teaql/tfp/TfpEndpointHandler.java +++ b/teaql-tfp-endpoint/src/main/java/io/teaql/tfp/TfpEndpointHandler.java @@ -87,6 +87,7 @@ private Map handleQueryActive(UserContext context, TrustedFedera } BaseRequest.TempRequest request = new BaseRequest.TempRequest(descriptor); + request.bindMetadata(metadata); if (root.has("limitValue") && !root.path("limitValue").isNull()) { int limit = root.path("limitValue").asInt(); diff --git a/teaql-tool-http/pom.xml b/teaql-tool-http/pom.xml index b4e90309..cf46b995 100644 --- a/teaql-tool-http/pom.xml +++ b/teaql-tool-http/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-tool-http diff --git a/teaql-utils-json/pom.xml b/teaql-utils-json/pom.xml index 69cbc5cf..17a81f99 100644 --- a/teaql-utils-json/pom.xml +++ b/teaql-utils-json/pom.xml @@ -6,7 +6,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE teaql-utils-json diff --git a/teaql-utils-reflection/pom.xml b/teaql-utils-reflection/pom.xml index 359bebbb..aa3ea01d 100644 --- a/teaql-utils-reflection/pom.xml +++ b/teaql-utils-reflection/pom.xml @@ -7,7 +7,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-utils-spring/pom.xml b/teaql-utils-spring/pom.xml index d455784a..f9e391af 100644 --- a/teaql-utils-spring/pom.xml +++ b/teaql-utils-spring/pom.xml @@ -7,7 +7,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml diff --git a/teaql-utils/pom.xml b/teaql-utils/pom.xml index 146e88b2..7394dede 100644 --- a/teaql-utils/pom.xml +++ b/teaql-utils/pom.xml @@ -7,7 +7,7 @@ io.teaql teaql-java-parent - 1.554-RELEASE + 1.555-RELEASE ../pom.xml