From 473becd7b2fd8361988a36dd26a84bbc699a5890 Mon Sep 17 00:00:00 2001 From: Konstantin Sobolev Date: Wed, 8 Nov 2017 17:21:45 -0800 Subject: [PATCH] op output projections: reversed `+` semantics --- .../examples/library/library.epigraph | 4 +- .../projections/ProjectionsParsingUtil.java | 13 +++- .../op/OpBasicProjectionPsiParser.java | 14 ++--- .../output/DefaultsPopulatingTransformer.java | 1 + .../output/OpOutputProjectionsPsiParser.java | 6 +- .../op/output/OpOutputProjectionsTest.java | 10 ++-- .../req/DefaultReqProjectionConstructor.java | 47 ++++++++------- .../DefaultReqProjectionConstructorTest.java | 53 +++++++++------- .../input/ReqInputProjectionParserTest.java | 60 +++++++++++-------- .../ReqOutputProjectionsParserTest.java | 38 ++++++------ .../epigraph/schema/parser/SchemaParser.java | 25 ++++++-- .../parser/psi/SchemaOpEntityTailItem.java | 3 + .../parser/psi/SchemaOpModelTailItem.java | 3 + .../psi/impl/SchemaOpEntityTailItemImpl.java | 6 ++ .../psi/impl/SchemaOpModelTailItemImpl.java | 6 ++ .../epigraph/schema/parser/grammar/schema.bnf | 6 +- .../epigraph/tests/GeneratedClassesTest.java | 18 +++--- .../ws/epigraph/tests/service.epigraph | 18 +++--- todo.md | 5 +- 19 files changed, 201 insertions(+), 135 deletions(-) diff --git a/examples/library/library-schema/src/main/epigraph/ws/epigraph/examples/library/library.epigraph b/examples/library/library-schema/src/main/epigraph/ws/epigraph/examples/library/library.epigraph index 4ecc188e5..5e4355c2d 100644 --- a/examples/library/library-schema/src/main/epigraph/ws/epigraph/examples/library/library.epigraph +++ b/examples/library/library-schema/src/main/epigraph/ws/epigraph/examples/library/library.epigraph @@ -51,9 +51,9 @@ resource books: map[BookId, BookRecord] { title, author :( id, - `record` (+firstName, middleName, +lastName) + `record` (firstName, +middleName, lastName) ), - text :+plain { + text :plain { ;offset: Long, // input parameter ;count: Long { default: 100 }, // input parameter with default meta: (offset, count) // supported meta-data projection diff --git a/java/projections-psi-parser-util/src/main/java/ws/epigraph/projections/ProjectionsParsingUtil.java b/java/projections-psi-parser-util/src/main/java/ws/epigraph/projections/ProjectionsParsingUtil.java index 471ac1783..e2f0061ff 100644 --- a/java/projections-psi-parser-util/src/main/java/ws/epigraph/projections/ProjectionsParsingUtil.java +++ b/java/projections-psi-parser-util/src/main/java/ws/epigraph/projections/ProjectionsParsingUtil.java @@ -130,8 +130,13 @@ TagApi findTag( if (retroTag == null) { if (type.kind() == TypeKind.ENTITY) return null; else tag = ((DatumTypeApi) type).self(); - } else - tag = retroTag; + } else { + if (op == null || op.tagProjections().containsKey(retroTag.name())) { + tag = retroTag; + } else { + tag = null; + } + } } else { tag = type.tagsMap().get(tagName); @@ -142,7 +147,9 @@ TagApi findTag( ); } - verifyTag(type, tag, op, location, context); + if (tag != null) + verifyTag(type, tag, op, location, context); + return tag; } diff --git a/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/OpBasicProjectionPsiParser.java b/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/OpBasicProjectionPsiParser.java index 838f5b161..093d3fba9 100644 --- a/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/OpBasicProjectionPsiParser.java +++ b/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/OpBasicProjectionPsiParser.java @@ -346,17 +346,15 @@ static List parseTails( if (tailPsi == null) tails = null; else { tails = new ArrayList<>(); - boolean flagged = rootProjection.flag(); // todo separate flag for tails - @Nullable SchemaOpEntityTailItem singleTail = tailPsi.getOpEntityTailItem(); if (singleTail == null) { @Nullable SchemaOpEntityMultiTail multiTail = tailPsi.getOpEntityMultiTail(); assert multiTail != null; for (SchemaOpEntityTailItem tailItem : multiTail.getOpEntityTailItemList()) { - tails.add(parseEntityTailItem(tailItem, flagged, dataType, rootProjection, typesResolver, context)); + tails.add(parseEntityTailItem(tailItem, dataType, rootProjection, typesResolver, context)); } } else { - tails.add(parseEntityTailItem(singleTail, flagged, dataType, rootProjection, typesResolver, context)); + tails.add(parseEntityTailItem(singleTail, dataType, rootProjection, typesResolver, context)); } SchemaProjectionPsiParserUtil.checkDuplicatingEntityTails(tails, context); @@ -367,7 +365,6 @@ static List parseTails( private static @NotNull OpEntityProjection parseEntityTailItem( final @NotNull SchemaOpEntityTailItem tailItem, - final boolean flagged, final @NotNull DataTypeApi dataType, final @NotNull OpEntityProjection rootProjection, final @NotNull TypesResolver typesResolver, @@ -377,7 +374,7 @@ static List parseTails( @NotNull SchemaOpEntityProjection psiTailProjection = tailItem.getOpEntityProjection(); return buildTailProjection( dataType, - flagged, + tailItem.getPlus() != null, tailTypeRef, psiTailProjection, rootProjection, @@ -965,6 +962,7 @@ else return parseUnnamedModelProjection( tailItemPsi.getTypeRef(), tailItemPsi.getOpModelProjection(), rootProjection, + tailItemPsi.getPlus() != null, typesResolver, context ) @@ -977,6 +975,7 @@ else return parseUnnamedModelProjection( singleTailPsi.getTypeRef(), singleTailPsi.getOpModelProjection(), rootProjection, + singleTailPsi.getPlus() != null, typesResolver, context ) @@ -995,6 +994,7 @@ else return parseUnnamedModelProjection( @NotNull SchemaTypeRef tailTypeRefPsi, @NotNull SchemaOpModelProjection modelProjectionPsi, @NotNull MP rootProjection, + boolean flagged, @NotNull TypesResolver typesResolver, @NotNull OpPsiProcessingContext context) throws PsiProcessingException { @@ -1006,7 +1006,7 @@ else return parseUnnamedModelProjection( MP mp = parseModelProjection( modelClass, tailType, - false, // todo add flags to tails? + flagged, modelProjectionPsi, rootProjection, typesResolver, diff --git a/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/output/DefaultsPopulatingTransformer.java b/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/output/DefaultsPopulatingTransformer.java index 06df23954..5227bd375 100644 --- a/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/output/DefaultsPopulatingTransformer.java +++ b/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/output/DefaultsPopulatingTransformer.java @@ -35,6 +35,7 @@ * @author Konstantin Sobolev */ public final class DefaultsPopulatingTransformer extends OpProjectionTransformer { + // todo unused, delete (since '+' semantics is reversed now) public DefaultsPopulatingTransformer() {} diff --git a/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/output/OpOutputProjectionsPsiParser.java b/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/output/OpOutputProjectionsPsiParser.java index 7dc194efb..7791ab80b 100644 --- a/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/output/OpOutputProjectionsPsiParser.java +++ b/java/projections-schema-psi-parser/src/main/java/ws/epigraph/projections/op/output/OpOutputProjectionsPsiParser.java @@ -17,7 +17,6 @@ package ws.epigraph.projections.op.output; import ws.epigraph.lang.MessagesContext; -import ws.epigraph.projections.op.CompositeOpProjectionTransformer; import ws.epigraph.projections.op.PostProcessingOpProjectionPsiParser; import ws.epigraph.projections.op.postprocess.OpFlagSynchronizer; @@ -29,10 +28,7 @@ public final class OpOutputProjectionsPsiParser extends PostProcessingOpProjecti public OpOutputProjectionsPsiParser(MessagesContext context) { super( null, - new CompositeOpProjectionTransformer( - new DefaultsPopulatingTransformer(), - new OpFlagSynchronizer("default", context) - ) + new OpFlagSynchronizer("default", context) ); } } diff --git a/java/projections-schema-psi-parser/tests/src/test/java/ws/epigraph/projections/op/output/OpOutputProjectionsTest.java b/java/projections-schema-psi-parser/tests/src/test/java/ws/epigraph/projections/op/output/OpOutputProjectionsTest.java index e171eda52..ed51d699f 100644 --- a/java/projections-schema-psi-parser/tests/src/test/java/ws/epigraph/projections/op/output/OpOutputProjectionsTest.java +++ b/java/projections-schema-psi-parser/tests/src/test/java/ws/epigraph/projections/op/output/OpOutputProjectionsTest.java @@ -541,15 +541,15 @@ public void testParseMapWithKeyProjection() throws PsiProcessingException { public void testFlag() throws PsiProcessingException { testParsingEntityProjection(":+id"); testParsingEntityProjection(":+`record` ( +id )"); - testParsingEntityProjection(":`record` ( id+ )", ":+`record` ( +id )"); - testParsingEntityProjection(":`record` ( bestFriend2+ )", ":+`record` ( +bestFriend2 :+id )"); - testParsingEntityProjection(":`record` ( +bestFriend2 )", ":+`record` ( +bestFriend2 :+id )"); + testParsingEntityProjection(":`record` ( id+ )", ":`record` ( +id )"); + testParsingEntityProjection(":`record` ( bestFriend2+ )", ":`record` ( bestFriend2 :+id )"); + testParsingEntityProjection(":`record` ( +bestFriend2 )", ":`record` ( +bestFriend2 :+id )"); // todo: enable smarter output in pretty printer - testParsingEntityProjection(":`record` ( friends*+:id )", ":+`record` ( +friends *+( :id ) )"); + testParsingEntityProjection(":`record` ( friends*+:id )", ":`record` ( friends *+( :id ) )"); testParsingEntityProjection( ":`record` ( friendsMap[forbidden]+:id)", - ":+`record` ( +friendsMap [ forbidden ]+( :id ) )" + ":`record` ( friendsMap [ forbidden ]+( :id ) )" ); testParsingEntityProjection(":`record` ( friendsMap2 { meta: +( start ) } [ required ]( :id ) )"); diff --git a/java/projections-url-psi-parser/src/main/java/ws/epigraph/url/projections/req/DefaultReqProjectionConstructor.java b/java/projections-url-psi-parser/src/main/java/ws/epigraph/url/projections/req/DefaultReqProjectionConstructor.java index e0a05ad07..4fd2b305f 100644 --- a/java/projections-url-psi-parser/src/main/java/ws/epigraph/url/projections/req/DefaultReqProjectionConstructor.java +++ b/java/projections-url-psi-parser/src/main/java/ws/epigraph/url/projections/req/DefaultReqProjectionConstructor.java @@ -25,7 +25,6 @@ import ws.epigraph.gdata.GDatum; import ws.epigraph.lang.TextLocation; import ws.epigraph.projections.ProjectionsParsingUtil; -import ws.epigraph.projections.abs.AbstractModelProjection; import ws.epigraph.projections.abs.AbstractTagProjectionEntry; import ws.epigraph.projections.gen.ProjectionReferenceName; import ws.epigraph.projections.op.*; @@ -46,14 +45,17 @@ @NotThreadSafe public class DefaultReqProjectionConstructor { public enum Mode { + /** Include nothing but $self and retro tags */ INCLUDE_NONE, - INCLUDE_FLAGGED_ONLY, + /** Include only things not flagged in op projection, plus $self and retro tags */ + INCLUDE_UNFLAGGED_ONLY, + /** Include everything */ INCLUDE_ALL } private final @NotNull Mode mode; private final boolean checkForRequiredMapKeys; - private final boolean copyFlagsFromOp; + private final boolean copyFlagsFromOp; // flags are currently inverted, until '+' on req means 'optional' (currently it means 'required') private final WeakHashMap visitedEntityRefs = new WeakHashMap<>(); @@ -68,7 +70,7 @@ public DefaultReqProjectionConstructor( } public static DefaultReqProjectionConstructor outputProjectionDefaultConstructor() { - return new DefaultReqProjectionConstructor(Mode.INCLUDE_FLAGGED_ONLY, true, false); + return new DefaultReqProjectionConstructor(Mode.INCLUDE_UNFLAGGED_ONLY, true, false); } public static DefaultReqProjectionConstructor inputProjectionDefaultConstructor(boolean includeAll) { @@ -107,22 +109,24 @@ public static DefaultReqProjectionConstructor updateProjectionDefaultConstructor final Iterable tags; Collection opTagEntries = op.tagProjections().values(); + @Nullable TagApi defaultTag = ProjectionsParsingUtil.findTag(dataType, null, op, location, context); switch (mode) { case INCLUDE_NONE: - @Nullable TagApi defaultTag = ProjectionsParsingUtil.findTag(dataType, null, op, location, context); tags = defaultTag == null ? Collections.emptyList() : Collections.singletonList(defaultTag); break; - case INCLUDE_FLAGGED_ONLY: + case INCLUDE_UNFLAGGED_ONLY: if (type.kind() == TypeKind.ENTITY) { - tags = opTagEntries - .stream() - .filter(tpe -> tpe.projection().flag()) - .map(AbstractTagProjectionEntry::tag) - .collect(Collectors.toList()); + tags = defaultTag == null ? + opTagEntries + .stream() + .filter(tpe -> !tpe.projection().flag()) + .map(AbstractTagProjectionEntry::tag) + .collect(Collectors.toList()) : + Collections.singletonList(defaultTag); } else { OpTagProjectionEntry opSingleTag = op.singleTagProjection(); assert opSingleTag != null; @@ -178,7 +182,8 @@ private ReqEntityProjection createDefaultEntityProjection( createDefaultModelProjection( ReqRecordModelProjection.class, tag.type(), - opTagProjection.projection(), copyFlagsFromOp && opTagProjection.projection().flag(), + opTagProjection.projection(), + copyFlagsFromOp && !opTagProjection.projection().flag(), null, Directives.EMPTY, datums, @@ -202,12 +207,12 @@ private ReqEntityProjection createDefaultEntityProjection( else { tails = new ArrayList<>(opTails.size()); for (final OpEntityProjection opTail : opTails) { - if (mode == Mode.INCLUDE_ALL || opTail.flag()) { + if (mode == Mode.INCLUDE_ALL || !opTail.flag()) { tails.add( createDefaultEntityProjection( opTail.type().dataType(), opTail, - copyFlagsFromOp && opTail.flag(), + copyFlagsFromOp && !opTail.flag(), datas, resolver, location, @@ -271,7 +276,7 @@ private ReqEntityProjection createDefaultEntityProjection( recordDatums.stream().map(rd -> rd._raw().getData((Field) fpe.field())) .collect(Collectors.toList()); - if ((mode == Mode.INCLUDE_ALL || fieldProjection.flag()) && (fieldDatas != null || datums == null)) { + if ((mode == Mode.INCLUDE_ALL || !fieldProjection.flag()) && (fieldDatas != null || datums == null)) { fields.put( entry.getKey(), new ReqFieldProjectionEntry( @@ -280,7 +285,7 @@ private ReqEntityProjection createDefaultEntityProjection( createDefaultEntityProjection( fpe.field().dataType(), fieldProjection.entityProjection(), - copyFlagsFromOp && fieldProjection.flag(), + copyFlagsFromOp && !fieldProjection.flag(), fieldDatas, resolver, location, @@ -434,14 +439,14 @@ private ReqEntityProjection createDefaultEntityProjection( return null; return Optional.ofNullable(op.metaProjection()).map(mp -> { - if (mode == Mode.INCLUDE_FLAGGED_ONLY && !mp.flag()) + if (mode == Mode.INCLUDE_UNFLAGGED_ONLY && mp.flag()) return null; try { return createDefaultModelProjection( modelClass, mp.type(), mp, - copyFlagsFromOp && mp.flag(), + copyFlagsFromOp && !mp.flag(), null, Directives.EMPTY, metaDatas, @@ -472,9 +477,9 @@ private ReqEntityProjection createDefaultEntityProjection( case INCLUDE_NONE: opTailsToInclude = null; break; - case INCLUDE_FLAGGED_ONLY: + case INCLUDE_UNFLAGGED_ONLY: opTailsToInclude = - opTailsToInclude.stream().filter(AbstractModelProjection::flag).collect(Collectors.toList()); + opTailsToInclude.stream().filter(projection -> !projection.flag()).collect(Collectors.toList()); break; case INCLUDE_ALL: // keep all } @@ -486,7 +491,7 @@ private ReqEntityProjection createDefaultEntityProjection( modelClass, ot.type(), ot, - copyFlagsFromOp && ot.flag(), + copyFlagsFromOp && !ot.flag(), null, Directives.EMPTY, datums, diff --git a/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/DefaultReqProjectionConstructorTest.java b/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/DefaultReqProjectionConstructorTest.java index e56e99b4c..f2eace6bb 100644 --- a/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/DefaultReqProjectionConstructorTest.java +++ b/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/DefaultReqProjectionConstructorTest.java @@ -76,7 +76,7 @@ public class DefaultReqProjectionConstructorTest { " friends *( :+id ),", " friendsMap []( :(id, `record` (id, +firstName) ) )", " friendsMap2 { meta: (+start, count) } [] (:id)", - " ) ~ws.epigraph.tests.UserRecord (profile)", + " ) ~ws.epigraph.tests.UserRecord +(profile)", ") :~ws.epigraph.tests.User :`record` (profile)" ) ); @@ -92,7 +92,7 @@ public void testIncludeFlaggedOnlyPrimitive() throws PsiProcessingException { ReqEntityProjection req = test( dataType, ReqTestUtil.parseOpOutputEntityProjection(dataType, "", resolver), - DefaultReqProjectionConstructor.Mode.INCLUDE_FLAGGED_ONLY, + DefaultReqProjectionConstructor.Mode.INCLUDE_UNFLAGGED_ONLY, "" ); @@ -130,7 +130,7 @@ public void testIncludeFlaggedOnlyPrimitiveList() throws PsiProcessingException ReqEntityProjection req = test( dataType, ReqTestUtil.parseOpOutputEntityProjection(dataType, "", resolver), - DefaultReqProjectionConstructor.Mode.INCLUDE_FLAGGED_ONLY, + DefaultReqProjectionConstructor.Mode.INCLUDE_UNFLAGGED_ONLY, "" // * :id ); @@ -149,15 +149,15 @@ public void testIncludeFlaggedOnlyPrimitiveList() throws PsiProcessingException public void testRetro() throws PsiProcessingException { test( dataType, - parsePersonOpOutputEntityProjection(":`record`(+bestFriend2)"), - DefaultReqProjectionConstructor.Mode.INCLUDE_FLAGGED_ONLY, + parsePersonOpOutputEntityProjection(":`record`(bestFriend2)"), + DefaultReqProjectionConstructor.Mode.INCLUDE_UNFLAGGED_ONLY, ":record ( bestFriend2 :id )" ); } @Test public void testMeta() throws PsiProcessingException { - OpEntityProjection op = parsePersonOpOutputEntityProjection(":`record`(friendsMap2 {meta:(+start,count)}[](:+id))"); + OpEntityProjection op = parsePersonOpOutputEntityProjection(":`record`(friendsMap2 {meta:(+start,count)}[](:id))"); test( dataType, @@ -169,8 +169,8 @@ public void testMeta() throws PsiProcessingException { test( dataType, op, - DefaultReqProjectionConstructor.Mode.INCLUDE_FLAGGED_ONLY, - ":record ( friendsMap2 @( start ) [ * ]( :id ) )" + DefaultReqProjectionConstructor.Mode.INCLUDE_UNFLAGGED_ONLY, + ":record ( friendsMap2 @( count ) [ * ]( :id ) )" ); test( @@ -182,17 +182,30 @@ public void testMeta() throws PsiProcessingException { } @Test - public void testIncludeFlaggedOnly() throws PsiProcessingException { - test(dataType, personOpProjection, DefaultReqProjectionConstructor.Mode.INCLUDE_FLAGGED_ONLY, + public void testIncludeUnflaggedOnly() throws PsiProcessingException { + test(dataType, personOpProjection, DefaultReqProjectionConstructor.Mode.INCLUDE_UNFLAGGED_ONLY, lines( - ":record (", - " bestFriend :( id, record ( id ;param2 = 333 ) ),", - " bestFriend2 $bf2 = :record ( id, bestFriend2 $bf2 ),", - " bestFriend3", - " :record ( bestFriend3 :record ( bestFriend3 :record ( bestFriend3 $bf3 = :record ( id, bestFriend3 $bf3 ) ) ) ),", - " friends *( :id ),", - " friendsMap [ * ]( :record ( firstName ) )", - ")" + ":(", + " id,", + " record (", + " id,", + " bestFriend :record ( bestFriend :record ( id, firstName ) ),", + " bestFriend2 $bf2 = :record ( bestFriend2 $bf2 ),", + " bestFriend3", + " :(", + " id,", + " record (", + " id,", + " firstName,", + " bestFriend3", + " :record ( id, lastName, bestFriend3 :record ( id, bestFriend3 $bf3 = :record ( bestFriend3 $bf3 ) ) )", + " )", + " ),", + " friends *( :() ),", + " friendsMap [ * ]( :( id, record ( id ) ) ),", + " friendsMap2 @( count ) [ * ]( :id )", + " )", + ") :~ws.epigraph.tests.User :record ( profile )" ) ); } @@ -266,8 +279,8 @@ public void testWithKeyParams() throws PsiProcessingException { public void testSingleTagNonParenthesized() throws PsiProcessingException { ReqEntityProjection req = test( dataType, - parsePersonOpOutputEntityProjection(":( id, `record`(+id) )"), - DefaultReqProjectionConstructor.Mode.INCLUDE_FLAGGED_ONLY, + parsePersonOpOutputEntityProjection(":( +id, `record`(id) )"), + DefaultReqProjectionConstructor.Mode.INCLUDE_UNFLAGGED_ONLY, ":record ( id )" ); assertFalse(req.parenthesized()); diff --git a/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/input/ReqInputProjectionParserTest.java b/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/input/ReqInputProjectionParserTest.java index 0b10e0252..7940819a5 100644 --- a/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/input/ReqInputProjectionParserTest.java +++ b/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/input/ReqInputProjectionParserTest.java @@ -88,22 +88,26 @@ public void testParseRecordTag() { ":record", lines( ":record (", - " id,", - " bestFriend :( +id, record ( +id, bestFriend :record ( id, firstName ) ) ),", - " bestFriend2 $bf2 = :record ( id, bestFriend2 $bf2 ),", - " bestFriend3", + " +id,", + " +bestFriend :( id, +record ( id, +bestFriend :+record ( +id, +firstName ) ) ),", + " +bestFriend2 $bf2 = :+record ( +id, +bestFriend2 $bf2 ),", + " +bestFriend3", " :(", - " id,", - " record (", - " id,", - " firstName,", - " bestFriend3", - " :record ( id, lastName, bestFriend3 :record ( id, bestFriend3 $bf3 = :record ( id, bestFriend3 $bf3 ) ) )", + " +id,", + " +record (", + " +id,", + " +firstName,", + " +bestFriend3", + " :+record (", + " +id,", + " +lastName,", + " +bestFriend3 :+record ( +id, +bestFriend3 $bf3 = :+record ( +id, +bestFriend3 $bf3 ) )", + " )", " )", " ),", - " friends *( :id ),", - " friendsMap [ * ]( :( id, record ( id, firstName ) ) )", - ") ~ws.epigraph.tests.UserRecord ( profile )" + " +friends *( :+id ),", + " +friendsMap [ * ]( :( +id, +record ( +id, +firstName ) ) )", + ") ~+ws.epigraph.tests.UserRecord ( +profile )" ) ); } @@ -116,22 +120,26 @@ public void testParseMultiTag() { ":(", " id,", " record (", - " id,", - " bestFriend :( +id, record ( +id, bestFriend :record ( id, firstName ) ) ),", - " bestFriend2 $bf2 = :record ( id, bestFriend2 $bf2 ),", - " bestFriend3", + " +id,", + " +bestFriend :( id, +record ( id, +bestFriend :+record ( +id, +firstName ) ) ),", + " +bestFriend2 $bf2 = :+record ( +id, +bestFriend2 $bf2 ),", + " +bestFriend3", " :(", - " id,", - " record (", - " id,", - " firstName,", - " bestFriend3", - " :record ( id, lastName, bestFriend3 :record ( id, bestFriend3 $bf3 = :record ( id, bestFriend3 $bf3 ) ) )", + " +id,", + " +record (", + " +id,", + " +firstName,", + " +bestFriend3", + " :+record (", + " +id,", + " +lastName,", + " +bestFriend3 :+record ( +id, +bestFriend3 $bf3 = :+record ( +id, +bestFriend3 $bf3 ) )", + " )", " )", " ),", - " friends *( :id ),", - " friendsMap [ * ]( :( id, record ( id, firstName ) ) )", - " ) ~ws.epigraph.tests.UserRecord ( profile )", + " +friends *( :+id ),", + " +friendsMap [ * ]( :( +id, +record ( +id, +firstName ) ) )", + " ) ~+ws.epigraph.tests.UserRecord ( +profile )", ")" ) ); diff --git a/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/output/ReqOutputProjectionsParserTest.java b/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/output/ReqOutputProjectionsParserTest.java index 5bd1fb380..d5344abf7 100644 --- a/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/output/ReqOutputProjectionsParserTest.java +++ b/java/projections-url-psi-parser/src/test/java/ws/epigraph/url/projections/req/output/ReqOutputProjectionsParserTest.java @@ -65,38 +65,38 @@ public class ReqOutputProjectionsParserTest { private final OpEntityProjection personOpProjection = parsePersonOpEntityProjection( lines( ":(", - " id,", + " +id,", " `record` (", - " id {", + " +id {", " ;param1 : epigraph.String { default: \"p1\" },", " ;param2 : ws.epigraph.tests.UserRecord", " },", - " +firstName{;param:epigraph.String},", - " middleName{", + " firstName{;param:epigraph.String},", + " +middleName{", " ;+param1:epigraph.String,", " ;+param2:epigraph.String { default: \"p2\" },", " ;param3:ws.epigraph.tests.PersonRecord (+firstName, bestFriend:`record`(+lastName))", " },", - " bestFriend :( id, `record` (", + " +bestFriend :( id, `record` (", " id,", " bestFriend :`record` (", " id,", " firstName", " ),", " ) ) :~ws.epigraph.tests.User : profile ,", - " bestFriend2 $bf2 = :( id, `record` ( id, bestFriend2 $bf2 ) ),", - " bestFriend3 :( id, `record` ( id, firstName, bestFriend3 :`record` ( id, lastName, bestFriend3 : `record` ( id, bestFriend3 $bf3 = :`record` ( id, bestFriend3 $bf3 ) ) ) ) ),", - " worstEnemy ( firstName ) ~ws.epigraph.tests.UserRecord $wu", - " worstUser $wu = ( id )", - " friends *( :(id,`record`(id)) ),", - " friendRecords * (id),", - " friendsMap [;keyParam:epigraph.String]( :(id, `record` (id, firstName) ) )", - " friendRecordMap [ forbidden ] (id, firstName)", - " ) ~ws.epigraph.tests.UserRecord (profile)", + " +bestFriend2 $bf2 = :( id, `record` ( id, bestFriend2 $bf2 ) ),", + " +bestFriend3 :( id, `record` ( id, firstName, bestFriend3 :`record` ( id, lastName, bestFriend3 : `record` ( id, bestFriend3 $bf3 = :`record` ( id, bestFriend3 $bf3 ) ) ) ) ),", + " +worstEnemy ( firstName ) ~ws.epigraph.tests.UserRecord $wu", + " +worstUser $wu = ( id )", + " +friends *( :(id,`record`(id)) ),", + " +friendRecords * (id),", + " +friendsMap [;keyParam:epigraph.String]( :(id, `record` (id, firstName) ) )", + " +friendRecordMap [ forbidden ] (id, firstName)", + " ) ~ws.epigraph.tests.UserRecord +(profile)", ") :~(", - " ws.epigraph.tests.User :`record` (profile)", + " ws.epigraph.tests.User +:`record` (profile)", " :~ws.epigraph.tests.SubUser :`record` (worstEnemy(id)),", - " ws.epigraph.tests.User2 :`record` (worstEnemy(id))", + " ws.epigraph.tests.User2 +:`record` (worstEnemy(id))", ")" ) ); @@ -356,7 +356,7 @@ public void testRequiredFieldWithTails() { public void testRequiredFieldMultiModel() { // + on field implies + on models // testParse(":record ( +bestFriend :( id, record ) )", ":record ( +bestFriend :( +id, +record ) )", 1); - testParse(":record ( +bestFriend :( id, record ) )", 1); + testParse(":record ( +bestFriend :( id, record ( id, bestFriend :record ( id, firstName ) ) ) )", 1); } @Test @@ -372,7 +372,7 @@ public void testDefaultProjection() { // now without default tag DataType dataType = new DataType(Person.type, null); - String opProjectionStr = ":( id, `record` ( +firstName ) )"; + String opProjectionStr = ":( +id, `record` ( firstName ) )"; OpEntityProjection opProjection = parseOpOutputEntityProjection(dataType, opProjectionStr, resolver); testParse( @@ -383,7 +383,7 @@ public void testDefaultProjection() { ); // tails - opProjectionStr = ":( id, `record` ( +firstName ) ) :~ws.epigraph.tests.User :`record` ( +lastName )"; + opProjectionStr = ":( +id, `record` ( firstName ) ) :~ws.epigraph.tests.User :`record` ( lastName )"; opProjection = parseOpOutputEntityProjection(dataType, opProjectionStr, resolver); testParse( diff --git a/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/SchemaParser.java b/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/SchemaParser.java index 64e77cedd..f281f8c77 100644 --- a/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/SchemaParser.java +++ b/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/SchemaParser.java @@ -2214,17 +2214,25 @@ public static boolean opEntityProjectionRef(PsiBuilder b, int l) { } /* ********************************************************** */ - // typeRef opEntityProjection + // typeRef '+'? opEntityProjection public static boolean opEntityTailItem(PsiBuilder b, int l) { if (!recursion_guard_(b, l, "opEntityTailItem")) return false; boolean r; Marker m = enter_section_(b, l, _NONE_, S_OP_ENTITY_TAIL_ITEM, ""); r = typeRef(b, l + 1); + r = r && opEntityTailItem_1(b, l + 1); r = r && opEntityProjection(b, l + 1); exit_section_(b, l, m, r, false, null); return r; } + // '+'? + private static boolean opEntityTailItem_1(PsiBuilder b, int l) { + if (!recursion_guard_(b, l, "opEntityTailItem_1")) return false; + consumeToken(b, S_PLUS); + return true; + } + /* ********************************************************** */ // opEntityPath public static boolean opFieldPath(PsiBuilder b, int l) { @@ -2822,17 +2830,25 @@ private static boolean opModelPropertyRecover_0(PsiBuilder b, int l) { } /* ********************************************************** */ - // typeRef opModelProjection + // typeRef '+'? opModelProjection public static boolean opModelTailItem(PsiBuilder b, int l) { if (!recursion_guard_(b, l, "opModelTailItem")) return false; boolean r; Marker m = enter_section_(b, l, _NONE_, S_OP_MODEL_TAIL_ITEM, ""); r = typeRef(b, l + 1); + r = r && opModelTailItem_1(b, l + 1); r = r && opModelProjection(b, l + 1); exit_section_(b, l, m, r, false, null); return r; } + // '+'? + private static boolean opModelTailItem_1(PsiBuilder b, int l) { + if (!recursion_guard_(b, l, "opModelTailItem_1")) return false; + consumeToken(b, S_PLUS); + return true; + } + /* ********************************************************** */ // ':' '(' (opMultiTagProjectionItem ','?)* ')' public static boolean opMultiTagProjection(PsiBuilder b, int l) { @@ -3897,7 +3913,7 @@ private static boolean recordDatumEntry_3(PsiBuilder b, int l) { } /* ********************************************************** */ - // ! ( ',' | ')' | qid ) + // ! ( ',' | ')' | qid | '+' ) static boolean recordModelProjectionRecover(PsiBuilder b, int l) { if (!recursion_guard_(b, l, "recordModelProjectionRecover")) return false; boolean r; @@ -3907,7 +3923,7 @@ static boolean recordModelProjectionRecover(PsiBuilder b, int l) { return r; } - // ',' | ')' | qid + // ',' | ')' | qid | '+' private static boolean recordModelProjectionRecover_0(PsiBuilder b, int l) { if (!recursion_guard_(b, l, "recordModelProjectionRecover_0")) return false; boolean r; @@ -3915,6 +3931,7 @@ private static boolean recordModelProjectionRecover_0(PsiBuilder b, int l) { r = consumeToken(b, S_COMMA); if (!r) r = consumeToken(b, S_PAREN_RIGHT); if (!r) r = qid(b, l + 1); + if (!r) r = consumeToken(b, S_PLUS); exit_section_(b, m, null, r); return r; } diff --git a/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/SchemaOpEntityTailItem.java b/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/SchemaOpEntityTailItem.java index ce3d3d89c..26ad2377c 100644 --- a/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/SchemaOpEntityTailItem.java +++ b/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/SchemaOpEntityTailItem.java @@ -29,4 +29,7 @@ public interface SchemaOpEntityTailItem extends PsiElement { @NotNull SchemaTypeRef getTypeRef(); + @Nullable + PsiElement getPlus(); + } diff --git a/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/SchemaOpModelTailItem.java b/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/SchemaOpModelTailItem.java index d3daa41db..e734c96da 100644 --- a/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/SchemaOpModelTailItem.java +++ b/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/SchemaOpModelTailItem.java @@ -29,4 +29,7 @@ public interface SchemaOpModelTailItem extends PsiElement { @NotNull SchemaTypeRef getTypeRef(); + @Nullable + PsiElement getPlus(); + } diff --git a/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/impl/SchemaOpEntityTailItemImpl.java b/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/impl/SchemaOpEntityTailItemImpl.java index 64d206e7d..1f5b75387 100644 --- a/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/impl/SchemaOpEntityTailItemImpl.java +++ b/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/impl/SchemaOpEntityTailItemImpl.java @@ -54,4 +54,10 @@ public SchemaTypeRef getTypeRef() { return notNullChild(PsiTreeUtil.getChildOfType(this, SchemaTypeRef.class)); } + @Override + @Nullable + public PsiElement getPlus() { + return findChildByType(S_PLUS); + } + } diff --git a/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/impl/SchemaOpModelTailItemImpl.java b/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/impl/SchemaOpModelTailItemImpl.java index 842cd3e0c..f35368c62 100644 --- a/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/impl/SchemaOpModelTailItemImpl.java +++ b/java/schema-parser-common/src/main/java.generated/ws/epigraph/schema/parser/psi/impl/SchemaOpModelTailItemImpl.java @@ -54,4 +54,10 @@ public SchemaTypeRef getTypeRef() { return notNullChild(PsiTreeUtil.getChildOfType(this, SchemaTypeRef.class)); } + @Override + @Nullable + public PsiElement getPlus() { + return findChildByType(S_PLUS); + } + } diff --git a/java/schema-parser-common/src/main/java/ws/epigraph/schema/parser/grammar/schema.bnf b/java/schema-parser-common/src/main/java/ws/epigraph/schema/parser/grammar/schema.bnf index 47b35708d..356c67cbd 100644 --- a/java/schema-parser-common/src/main/java/ws/epigraph/schema/parser/grammar/schema.bnf +++ b/java/schema-parser-common/src/main/java/ws/epigraph/schema/parser/grammar/schema.bnf @@ -513,11 +513,11 @@ opMultiTagProjectionItem ::= '+'? tagName opModelProjection opEntityPolymorphicTail ::= ':' '~' ( opEntityTailItem | opEntityMultiTail ) {pin=2} opEntityMultiTail ::= '(' (opEntityTailItem ','?)* ')' {pin=1} -opEntityTailItem ::= typeRef opEntityProjection +opEntityTailItem ::= typeRef '+'? opEntityProjection // todo opModelPolymorphicTail ::= '~' ( opModelTailItem | opModelMultiTail ) // {pin=1} opModelMultiTail ::= '(' (opModelTailItem ','?)* ')' {pin=1} -opModelTailItem ::= typeRef opModelProjection +opModelTailItem ::= typeRef '+'? opModelProjection // todo opModelProjection ::= opNamedModelProjection | opUnnamedOrRefModelProjection opUnnamedOrRefModelProjection ::= opModelProjectionRef | opUnnamedModelProjection @@ -542,7 +542,7 @@ opModelMeta ::= 'meta' ':' '+'? opModelProjection // op output record opRecordModelProjection ::= '(' (opFieldProjectionEntry ','?)* ')' { pin=1 } opFieldProjectionEntry ::= '+'? qid opFieldProjection { pin=2 recoverWhile=recordModelProjectionRecover} -private recordModelProjectionRecover ::= ! ( ',' | ')' | qid ) +private recordModelProjectionRecover ::= ! ( ',' | ')' | qid | '+' ) opFieldProjection ::= opEntityProjection // op output list diff --git a/tests/schema-java/src/test/java/ws/epigraph/tests/GeneratedClassesTest.java b/tests/schema-java/src/test/java/ws/epigraph/tests/GeneratedClassesTest.java index e70329a96..12939d5ff 100644 --- a/tests/schema-java/src/test/java/ws/epigraph/tests/GeneratedClassesTest.java +++ b/tests/schema-java/src/test/java/ws/epigraph/tests/GeneratedClassesTest.java @@ -165,44 +165,44 @@ public void testOpOutputDefaults() { OpFieldProjection fieldProjection = ws.epigraph.tests._resources.users.UsersResourceDeclaration.readOperationDeclaration.outputProjection(); OpEntityProjection entityProjection = fieldProjection.entityProjection(); - assertTrue(entityProjection.flag()); + assertFalse(entityProjection.flag()); OpTagProjectionEntry tpe = entityProjection.singleTagProjection(); // self assertNotNull(tpe); OpModelProjection modelProjection = tpe.projection(); - assertTrue(modelProjection.flag()); + assertFalse(modelProjection.flag()); assertTrue(modelProjection instanceof OpMapModelProjection); OpMapModelProjection mapModelProjection = (OpMapModelProjection) modelProjection; entityProjection = mapModelProjection.itemsProjection(); - assertTrue(entityProjection.flag()); + assertFalse(entityProjection.flag()); tpe = entityProjection.tagProjection("id"); assertNotNull(tpe); - assertFalse(tpe.projection().flag()); + assertTrue(tpe.projection().flag()); tpe = entityProjection.tagProjection("record"); assertNotNull(tpe); modelProjection = tpe.projection(); - assertTrue(modelProjection.flag()); + assertFalse(modelProjection.flag()); assertTrue(modelProjection instanceof OpRecordModelProjection); OpRecordModelProjection recordModelProjection = (OpRecordModelProjection) modelProjection; OpFieldProjectionEntry fpe = recordModelProjection.fieldProjection("id"); assertNotNull(fpe); - assertFalse(fpe.fieldProjection().flag()); + assertTrue(fpe.fieldProjection().flag()); fpe = recordModelProjection.fieldProjection("firstName"); assertNotNull(fpe); - assertTrue(fpe.fieldProjection().flag()); + assertFalse(fpe.fieldProjection().flag()); fpe = recordModelProjection.fieldProjection("lastName"); assertNotNull(fpe); - assertTrue(fpe.fieldProjection().flag()); + assertFalse(fpe.fieldProjection().flag()); fpe = recordModelProjection.fieldProjection("bestFriend"); assertNotNull(fpe); - assertFalse(fpe.fieldProjection().flag()); + assertTrue(fpe.fieldProjection().flag()); } @Test diff --git a/tests/schema/src/main/epigraph/ws/epigraph/tests/service.epigraph b/tests/schema/src/main/epigraph/ws/epigraph/tests/service.epigraph index e6ac438b4..caee017f2 100644 --- a/tests/schema/src/main/epigraph/ws/epigraph/tests/service.epigraph +++ b/tests/schema/src/main/epigraph/ws/epigraph/tests/service.epigraph @@ -27,12 +27,12 @@ resource users: PersonMap { read { outputProjection { ;start: Long, ;count: Long, meta: (start, count) } []( :( - id, + +id, `record`( - id, - +firstName, - +lastName, - bestFriend:( + +id, + firstName, + lastName, + +bestFriend:( id, `record`( id, firstName, lastName, bestFriend:id, worstEnemy ) ):~User:( @@ -40,16 +40,16 @@ resource users: PersonMap { worstEnemy( id, firstName, lastName, profile ) profile ) - ) - worstEnemy( id, firstName, lastName ) ~UserRecord( profile ) - friends *( + ), + +worstEnemy( id, firstName, lastName ) ~UserRecord( profile ), + +friends *( :( id, `record`( id, firstName, lastName ) ) ) ) - ):~User:( + ):~User +:( id, `record`( profile, worstEnemy( profile ) ) ) diff --git a/todo.md b/todo.md index fe5247f30..749452014 100644 --- a/todo.md +++ b/todo.md @@ -40,7 +40,7 @@ - [ ] req projections codegen: a lot of code duplication, move stuff up (but don't kill extras like 'required' and 'replace') - [x] codegen bug: see `childProjectionWithUnusedParent.epigraph` and `tests-schema-java.gradle` - [x] codegen bug: impossible to have tail type as a field, see `childUsedByParent.epigraph` -- [ ] codegen: a hierarchy of `N` record types with `N` new fields each will result in `N^3` lines of code, since +- [x] codegen: a hierarchy of `N` record types with `N` new fields each will result in `N^3` lines of code, since every new type assembler has to include all parent field assemblers. Proposed way out: https://sumologic.slack.com/archives/D0JPD1FKN/p1509569946000092 @@ -89,7 +89,7 @@ - [ ] (?) post-parsing req projections validations/transformations should (also?) actually happen in filter chains, otherwise it won't affect projections constructed using builders. On the other hand if it only happens in filter chains it will be hard to report errors with pointers to the original parsed string. -- [ ] reverse the meaning of `+` on op output projections: it should be used to mark "expensive" (do not include by default) fields +- [x] reverse the meaning of `+` on op output projections: it should be used to mark "expensive" (do not include by default) fields - if field is datum type: return `$self` - else if field has retro: use retro tag - else return all tags not marked as `+` @@ -119,6 +119,7 @@ be represented by entity-model projections pair, this leads to messy code - [x] req projections syntax: allow model tail references - [ ] BIG move flag from entity projections to fields/collection items. Because: +- [ ] op output parser: check if 'include in default' fields have required parameters without defaults ``` outputProjection user: User = :( rec (