All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.wjybxx.dson.ext.Projection Maven / Gradle / Ivy

The newest version!
package cn.wjybxx.dson.ext;

import cn.wjybxx.base.CollectionUtils;
import cn.wjybxx.dson.*;
import cn.wjybxx.dson.io.DsonIOException;
import cn.wjybxx.dson.text.DsonTextReader;
import cn.wjybxx.dson.text.DsonTextReaderSettings;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 投影
 * 

多路径表达式

* 多路径表达式需要使用标准的Dson文本表达。 *
{@code
 *  {
 *    name: 1,
 *    age: 0, // 不返回age字段
 *    pos: {
 *      $header: 1, // 返回pos的header
 *      $all: 1, // 返回pos的全部字段 -- 可表示这是一个object映射
 *      z: 0 // 排除z
 *   },
 *   arr1: {
 *      $slice: 1, // $slice 用于对数组进行切片投影
 *      $elem: {  // $elem 用于对数组元素进行投影
 *         name: 1,
 *         pos: 1
 *      }
 *   },
 *   arr2: {$slice: 1}, // 跳过1个元素,选择剩余所有元素
 *   arr3: {$slice: [0, 5]}, // 选择数组的前5个元素、
 *   arr4: 1, // 返回arr4整个数组
 *
 *   key1: {}, // 如果key1存在,则返回对应空Object/空Array。
 *   key2: {$header: 1}, // // 如果key2存在,返回的空Object或空Array将包含header。
 *  }
 * }
* *

规则

* 1. $header 表示投影对象的header,header总是全量投影;header默认不返回,只有显式指定的情况下返回; * 2. value为1表示选择,为0表示排除;全为0时表示反选模式,否则只返回value为1的字段 -- header不计入。 * 3. $all 用于选择object的所有字段,强制为反选字段模式;主要解决声明header的影响,也方便进入排除模式。 * 4. 如果无法根据投影信息确定投影值的类型,将由真实的数据决定返回值类型 -- 可用于测试数据类型。 * 5. $slice 表示数组范围投影,对数组进行细粒度投影时必须声明$slice,否则返回空数组。 * 6. $slice skip 表示跳过skip个元素,截取剩余部分;兼容 $slice [skip]输入; * 7. $slice [skip, count] 表示跳过skip个元素,截取指定个数的元素部分; * 8. $elem 表示数组元素进行投影。 * 9. Object的投影为Object,Array的投影为Array。 * 10. 点号'.'默认不是路径分隔符,需要快捷语法时需要用户自行定义。 *

* Q:为什么不支持反向索引? * A:我们不会在普通配置上存储数组的元素个数,因此反向索引必须解析所有的数组元素,用户直接获取所有元素即可。。。 * 数据库通常会支持反向索引,这是因为数据库数据不是手工直接修改的,因此数据库可以在数据上存储一些元数据,实现快速截取。 * * @author wjybxx * date - 2023/12/29 */ @Immutable public class Projection { /** 用于选择header */ public static final String KEY_HEADER = "$header"; /** 用于强调投影为object */ public static final String KEY_OBJECT = "$object"; /** 用于选择Object内的所有键 */ public static final String KEY_ALL = "$all"; /** 用于强调投影为数组 */ public static final String KEY_ARRAY = "$array"; /** 用于对数组切片 */ public static final String KEY_SLICE = "$slice"; /** 用于对数组元素进行投影 */ public static final String KEY_ELEM = "$elem"; /** object投影的特殊键 */ public static final Set OBJECT_KEYS = Set.of(KEY_OBJECT, KEY_ALL); /** 数组投影的特殊键 */ public static final Set ARRAY_KEYS = Set.of(KEY_ARRAY, KEY_SLICE, KEY_ELEM); /** 所有的特殊键 */ public static final Set ALL_SPECIAL_KEYS; static { HashSet tempAllKeys = CollectionUtils.newHashSet(8); tempAllKeys.add(KEY_HEADER); tempAllKeys.addAll(OBJECT_KEYS); tempAllKeys.addAll(ARRAY_KEYS); ALL_SPECIAL_KEYS = Set.copyOf(tempAllKeys); } /** 表达式的根节点 */ private final Node root; public Projection(String projectInfo) { this(Dsons.fromDson(projectInfo).asObject()); } public Projection(DsonObject projectInfo) { root = parseNode(projectInfo); } /** 将给定Dson字符串进行投影 */ public DsonValue project(String dsonString) { return project(new DsonTextReader(DsonTextReaderSettings.DEFAULT, dsonString)); } /** * 1.如果投影为Array,则投可投影顶层的Header,返回值必定为{@link DsonArray} * 2.如果投影为Object,则只返回第一个对象的投影,顶层Header被当做普通对象投影。 */ public DsonValue project(DsonReader reader) { if (root instanceof DefaultNode node && node.arrayLike) { return new Matcher(reader, root).projectTopArray(); } else { DsonType dsonType = reader.readDsonType(); if (dsonType == DsonType.END_OF_OBJECT) { return null; } Matcher matcher = new Matcher(reader, root); return matcher.project(); } } private static class Matcher { final DsonReader reader; final Node node; private Matcher(DsonReader reader, Node node) { this.reader = reader; this.node = node; } DsonValue project() { DsonType currentDsonType = reader.getCurrentDsonType(); if (!node.testType(currentDsonType)) { reader.skipValue(); // 上下文不匹配时返回期望的类型 if (node instanceof DefaultNode defaultNode) { return defaultNode.arrayLike ? new DsonArray<>(0) : new DsonObject<>(0); } // 语义不清楚的情况下返回真实的类型 if (currentDsonType == DsonType.ARRAY) { return new DsonArray<>(0); } if (currentDsonType == DsonType.HEADER) { return new DsonHeader<>(); } return new DsonObject<>(0); } if (currentDsonType == DsonType.ARRAY) { return projectArray(); } if (currentDsonType == DsonType.HEADER) { return projectHeader(); } return projectObject(); } private static boolean needMatcher(Node fieldNode) { return fieldNode.isProjectNode() && !(fieldNode instanceof SelectNode); } private DsonHeader projectHeader() { DsonHeader dsonObject = new DsonHeader<>(); DsonType dsonType; String name; DsonValue value; reader.readStartHeader(); while ((dsonType = reader.readDsonType()) != DsonType.END_OF_OBJECT) { name = reader.readName(); if (node.testField(name)) { Node fieldNode = node.getFieldNode(name); if (needMatcher(fieldNode)) { value = new Matcher(reader, fieldNode).project(); } else { value = Dsons.readDsonValue(reader); } dsonObject.put(name, value); } else { reader.skipValue(); } } reader.readEndHeader(); return dsonObject; } DsonObject projectObject() { DsonObject dsonObject = new DsonObject<>(); DsonType dsonType; String name; DsonValue value; reader.readStartObject(); while ((dsonType = reader.readDsonType()) != DsonType.END_OF_OBJECT) { if (dsonType == DsonType.HEADER) { if (node.testHeader()) { Dsons.readHeader(reader, dsonObject.getHeader()); } else { reader.skipValue(); } if (node.remainCount(dsonObject.size()) == 0) { reader.skipToEndOfObject(); // 不再继续读;header不在计数中,因此放header后 break; } continue; } name = reader.readName(); if (node.testField(name)) { Node fieldNode = node.getFieldNode(name); if (needMatcher(fieldNode)) { value = new Matcher(reader, fieldNode).project(); } else { value = Dsons.readDsonValue(reader); } dsonObject.put(name, value); if (node.remainCount(dsonObject.size()) == 0) { reader.skipToEndOfObject(); break; } } else { reader.skipValue(); } } reader.readEndObject(); return dsonObject; } DsonArray projectArray() { DsonArray dsonArray = new DsonArray<>(); DsonType dsonType; DsonValue value; int index = 0; reader.readStartArray(); while ((dsonType = reader.readDsonType()) != DsonType.END_OF_OBJECT) { if (dsonType == DsonType.HEADER) { if (node.testHeader()) { Dsons.readHeader(reader, dsonArray.getHeader()); } else { reader.skipValue(); } if (node.remainCount(dsonArray.size()) == 0) { reader.skipToEndOfObject(); // 不再继续读;header不在计数中,因此放header后 break; } continue; } if (node.testElement(index++)) { Node elemNode = node.getElemNode(); if (needMatcher(elemNode)) { value = new Matcher(reader, elemNode).project(); } else { value = Dsons.readDsonValue(reader); } dsonArray.add(value); if (node.remainCount(dsonArray.size()) == 0) { reader.skipToEndOfObject(); break; } } else { reader.skipValue(); } } reader.readEndArray(); return dsonArray; } DsonArray projectTopArray() { DsonArray dsonArray = new DsonArray<>(); DsonType dsonType; DsonValue value; int index = 0; while ((dsonType = reader.readDsonType()) != DsonType.END_OF_OBJECT) { if (dsonType == DsonType.HEADER) { if (node.testHeader()) { Dsons.readHeader(reader, dsonArray.getHeader()); } else { reader.skipValue(); } if (node.remainCount(dsonArray.size()) == 0) { break; // 不再继续读;header不在计数中,因此放header后 } continue; } if (node.testElement(index++)) { Node elemNode = node.getElemNode(); if (needMatcher(elemNode)) { value = new Matcher(reader, elemNode).project(); } else { value = Dsons.readDsonValue(reader); } dsonArray.add(value); if (node.remainCount(dsonArray.size()) == 0) { break; } } else { reader.skipValue(); } } return dsonArray; } } // region node private static final Node DISCARD_NODE = new DiscardNode(); private static final Node SELECT_NODE = new SelectNode(); /** * Node表示预编译的节点 * 1.不能总是解析用户的文本,因此需要提前编译缓存 * 2.Node仅仅是保存编译后的数据,即包含Object的投影信息,也包含Array的投影信息。 * 3.只有在真正执行投影时才知道数据的类型,因此Node不能直接运行 * 4.node无需记录父子关系,Matcher记录即可。 */ private static abstract class Node { /** 是否是需要投影的节点 */ public final boolean isProjectNode() { return !(this instanceof DiscardNode); } /** 测试节点类型是否匹配 */ public abstract boolean testType(DsonType dsonType); /** 测试对象的header是否需要投影 */ public abstract boolean testHeader(); /** 测试对象的特定字段是否需要返回 */ public abstract boolean testField(String key); /** 测试数组的特点下标元素是否需要返回 */ public abstract boolean testElement(int index); /** 剩余需要投影的成员数量,-1表示未知 */ public abstract int remainCount(int current); /** 获取字段投影的Node信息 */ @Nonnull public abstract Node getFieldNode(String key); /** 获取数组元素投影的Node信息 */ @Nonnull public abstract Node getElemNode(); } /** DefaultNode代表的是{k:v}构建的节点 */ private static class DefaultNode extends Node { /** 匹配的上下文是否是数组类型 */ final boolean arrayLike; /** 是否投影header */ final boolean includeHeader; /** object的投影模式 */ final SelectMode selectMode; /** 字段的投影信息 */ final Map fieldNodes; /** 被选择的字段数 */ final int selectCount; /** 数组切片信息 */ final SliceSpec sliceSpec; /** 数组元素投影信息 */ final Node elementNode; public DefaultNode(boolean arrayLike, DsonObject projectInfo) { this.arrayLike = arrayLike; this.includeHeader = isTrue(projectInfo.get(KEY_HEADER)); // object 字段映射 { fieldNodes = CollectionUtils.newHashMap(projectInfo.size()); int count = 0; for (Map.Entry entry : projectInfo.entrySet()) { String key = entry.getKey(); if (ALL_SPECIAL_KEYS.contains(key)) { continue; } DsonValue value = entry.getValue(); Node childNode = parseNode(value); fieldNodes.put(key, childNode); if (childNode.isProjectNode()) { count++; } } this.selectCount = count; final DsonValue allValue = projectInfo.get(KEY_ALL); if (isTrue(allValue)) { // 指定$all的情况下直接进入反选模式 selectMode = SelectMode.INVERT; } else if (!fieldNodes.isEmpty() && count == 0) { // 指定了key,且所有key的value都是0 selectMode = SelectMode.INVERT; } else { selectMode = SelectMode.NORMAL; } } // array 映射 { final DsonValue sliceValue = projectInfo.get(KEY_SLICE); if (sliceValue == null) { // 未声明slice的情况下,返回空数组 sliceSpec = SliceSpec.EMPTY; } else { sliceSpec = parseSliceSpec(sliceValue); } DsonValue elemValue = projectInfo.get(KEY_ELEM); if (elemValue == null) { // 未声明elem的情况下,返回原始对象 elementNode = SELECT_NODE; } else { elementNode = parseNode(elemValue); } } } @Override public boolean testType(DsonType dsonType) { return arrayLike ? dsonType == DsonType.ARRAY : dsonType.isObjectLike(); } public boolean testHeader() { return includeHeader; } public boolean testField(String key) { if (arrayLike) { return false; } Node node = fieldNodes.get(key); if (this.selectMode == SelectMode.NORMAL) { return node != null && node.isProjectNode(); } return node == null || node.isProjectNode(); } public boolean testElement(int index) { if (!arrayLike) { return false; } if (index < sliceSpec.skip) { return false; } if (sliceSpec.count == -1) { // 全投影 return true; } return index < sliceSpec.skip + sliceSpec.count; // 有限投影 } @Override public int remainCount(int current) { if (arrayLike) { if (sliceSpec.count == -1) { return -1; } return Math.max(0, sliceSpec.count - current); } return selectMode == SelectMode.NORMAL ? Math.max(0, selectCount - current) : -1; } @Nonnull @Override public Node getFieldNode(String key) { if (arrayLike) { return DISCARD_NODE; } if (selectMode == SelectMode.NORMAL) { return fieldNodes.getOrDefault(key, DISCARD_NODE); } return fieldNodes.getOrDefault(key, SELECT_NODE); } @Nonnull @Override public Node getElemNode() { if (!arrayLike) { return DISCARD_NODE; } return elementNode; } } /** 无法识别上下文类型的Node -- 比如:{}, {$header: 1} */ private static class UnknownContextNode extends Node { final boolean includeHeader; public UnknownContextNode(DsonObject projectInfo) { includeHeader = isTrue(projectInfo.get(KEY_HEADER)); } @Override public boolean testType(DsonType dsonType) { return true; } @Override public boolean testHeader() { return includeHeader; } @Override public boolean testField(String key) { return false; } @Override public boolean testElement(int index) { return false; } @Override public int remainCount(int current) { return 0; } @Nonnull @Override public Node getFieldNode(String key) { return DISCARD_NODE; } @Nonnull @Override public Node getElemNode() { return DISCARD_NODE; } } /** 简单丢弃节点 -- value为0 */ private static class DiscardNode extends Node { @Override public boolean testType(DsonType dsonType) { return false; } @Override public boolean testHeader() { return false; } @Override public boolean testField(String key) { return false; } @Override public boolean testElement(int index) { return false; } @Override public int remainCount(int current) { return 0; } @Nonnull @Override public Node getFieldNode(String key) { return this; } @Nonnull @Override public Node getElemNode() { return this; } } /** 简单选择节点 -- value为1 */ private static class SelectNode extends Node { @Override public boolean testType(DsonType dsonType) { return true; } @Override public boolean testHeader() { return false; // header默认被忽略 } @Override public boolean testField(String key) { return true; } @Override public boolean testElement(int index) { return true; } @Override public int remainCount(int current) { return -1; } @Nonnull @Override public Node getFieldNode(String key) { return this; } @Nonnull @Override public Node getElemNode() { return this; } } private static Node parseNode(DsonValue childSpec) { Objects.requireNonNull(childSpec); if (childSpec instanceof DsonBool dsonBool) { // true or false return dsonBool.getValue() ? SELECT_NODE : DISCARD_NODE; } if (childSpec instanceof DsonNumber dsonNumber) { // 0 or 1 return dsonNumber.intValue() == 1 ? SELECT_NODE : DISCARD_NODE; } DsonObject childProjInfo = childSpec.asObject(); if (childProjInfo.isEmpty()) { // {} return new UnknownContextNode(childProjInfo); } if (childProjInfo.size() == 1 && childProjInfo.containsKey(KEY_HEADER)) { // {$header: 1} return new UnknownContextNode(childProjInfo); } for (String arrayKey : ARRAY_KEYS) { if (childProjInfo.containsKey(arrayKey)) { // {$slice: 1} return new DefaultNode(true, childProjInfo); } } // 默认为object上下文 return new DefaultNode(false, childProjInfo); } private static boolean isTrue(DsonValue dsonValue) { if (dsonValue == null) return false; if (dsonValue.getDsonType() == DsonType.BOOL) { return dsonValue.asBool(); } if (dsonValue.isNumber()) { return dsonValue.asDsonNumber().intValue() == 1; } return false; } private static SliceSpec parseSliceSpec(DsonValue rangeValue) { if (rangeValue.isNumber()) { int skip = rangeValue.asDsonNumber().intValue(); return new SliceSpec(skip); } // todo 将slice看做字符串数组,就可以解析正负号 DsonArray array = rangeValue.asArray(); return switch (array.size()) { case 0 -> SliceSpec.EMPTY; case 1 -> { int skip = array.get(0).asDsonNumber().intValue(); yield new SliceSpec(skip); } case 2 -> { int skip = array.get(0).asDsonNumber().intValue(); int count = array.get(1).asDsonNumber().intValue(); yield new SliceSpec(skip, count); } default -> { throw new DsonIOException("invalid slice range: " + rangeValue); } }; } /** object类型的投影模式 */ private enum SelectMode { /** 普通选择,选择给定的键 -- 投影信息为空 或 value包含至少一个1 */ NORMAL, /** 反选,排除给定的键 -- 投影信息value全部为0 */ INVERT, } /** 数组切片范围 */ private static class SliceSpec { static final SliceSpec EMPTY = new SliceSpec(0, 0); static final SliceSpec FIRST = new SliceSpec(0, 1); static final SliceSpec FULL = new SliceSpec(0, -1); final int skip; final int count; public SliceSpec(int skip) { this.skip = skip; this.count = -1; } public SliceSpec(int skip, int count) { this.skip = skip; this.count = count; } } // endregion }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy