提交 aa3c98ba authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Allow any expressions in window frames

上级 f27c631e
...@@ -3294,7 +3294,7 @@ public class Parser { ...@@ -3294,7 +3294,7 @@ public class Parser {
read(ROW); read(ROW);
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null); return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
} }
Expression value = readValueOrParameter(); Expression value = readExpression();
read("PRECEDING"); read("PRECEDING");
return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value); return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value);
} }
...@@ -3311,7 +3311,7 @@ public class Parser { ...@@ -3311,7 +3311,7 @@ public class Parser {
read(ROW); read(ROW);
return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null); return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null);
} }
Expression value = readValueOrParameter(); Expression value = readExpression();
if (readIf("PRECEDING")) { if (readIf("PRECEDING")) {
return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value); return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value);
} }
...@@ -3319,16 +3319,6 @@ public class Parser { ...@@ -3319,16 +3319,6 @@ public class Parser {
return new WindowFrameBound(WindowFrameBoundType.FOLLOWING, value); return new WindowFrameBound(WindowFrameBoundType.FOLLOWING, value);
} }
private Expression readValueOrParameter() {
int index = parseIndex;
Expression value = readExpression();
if (!(value instanceof ValueExpression) && !(value instanceof Parameter)) {
parseIndex = index;
throw getSyntaxError();
}
return value;
}
private AggregateType getAggregateType(String name) { private AggregateType getAggregateType(String name) {
if (!identifiersToUpper) { if (!identifiersToUpper) {
// if not yet converted to uppercase, do it now // if not yet converted to uppercase, do it now
......
...@@ -98,7 +98,9 @@ public abstract class AbstractAggregate extends DataAnalysisOperation { ...@@ -98,7 +98,9 @@ public abstract class AbstractAggregate extends DataAnalysisOperation {
aggregateFastPartition(session, result, ordered, rowIdColumn, grouped); aggregateFastPartition(session, result, ordered, rowIdColumn, grouped);
return; return;
} }
if (frame.getExclusion() == WindowFrameExclusion.EXCLUDE_NO_OTHERS) { if (frame.isVariableBounds()) {
grouped = false;
} else if (frame.getExclusion() == WindowFrameExclusion.EXCLUDE_NO_OTHERS) {
WindowFrameBound following = frame.getFollowing(); WindowFrameBound following = frame.getFollowing();
boolean unboundedFollowing = following != null boolean unboundedFollowing = following != null
&& following.getType() == WindowFrameBoundType.UNBOUNDED_FOLLOWING; && following.getType() == WindowFrameBoundType.UNBOUNDED_FOLLOWING;
...@@ -117,10 +119,11 @@ public abstract class AbstractAggregate extends DataAnalysisOperation { ...@@ -117,10 +119,11 @@ public abstract class AbstractAggregate extends DataAnalysisOperation {
} }
// All other types of frames (slow) // All other types of frames (slow)
int size = ordered.size(); int size = ordered.size();
int frameParametersOffset = getWindowFrameParametersOffset();
for (int i = 0; i < size;) { for (int i = 0; i < size;) {
Object aggregateData = createAggregateData(); Object aggregateData = createAggregateData();
for (Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, for (Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
false); iter.hasNext();) { frameParametersOffset, i, false); iter.hasNext();) {
updateFromExpressions(session, aggregateData, iter.next()); updateFromExpressions(session, aggregateData, iter.next());
} }
Value r = getAggregatedValue(session, aggregateData); Value r = getAggregatedValue(session, aggregateData);
......
...@@ -59,14 +59,19 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -59,14 +59,19 @@ public abstract class DataAnalysisOperation extends Expression {
*/ */
protected SortOrder overOrderBySort; protected SortOrder overOrderBySort;
private int numFrameExpressions;
private int lastGroupRowId; private int lastGroupRowId;
/** /**
* Create sort order. * Create sort order.
* *
* @param session database session * @param session
* @param orderBy array of order by expressions * database session
* @param offset index offset * @param orderBy
* array of order by expressions
* @param offset
* index offset
* @return the SortOrder * @return the SortOrder
*/ */
protected static SortOrder createOrder(Session session, ArrayList<SelectOrderBy> orderBy, int offset) { protected static SortOrder createOrder(Session session, ArrayList<SelectOrderBy> orderBy, int offset) {
...@@ -131,9 +136,12 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -131,9 +136,12 @@ public abstract class DataAnalysisOperation extends Expression {
/** /**
* Map the columns of the resolver to expression columns. * Map the columns of the resolver to expression columns.
* *
* @param resolver the column resolver * @param resolver
* @param level the subquery nesting level * the column resolver
* @param innerState one of the Expression MAP_IN_* values * @param level
* the subquery nesting level
* @param innerState
* one of the Expression MAP_IN_* values
*/ */
protected void mapColumnsAnalysis(ColumnResolver resolver, int level, int innerState) { protected void mapColumnsAnalysis(ColumnResolver resolver, int level, int innerState) {
if (over != null) { if (over != null) {
...@@ -151,6 +159,18 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -151,6 +159,18 @@ public abstract class DataAnalysisOperation extends Expression {
} else if (!isAggregate()) { } else if (!isAggregate()) {
overOrderBySort = new SortOrder(session.getDatabase(), new int[getNumExpressions()], new int[0], null); overOrderBySort = new SortOrder(session.getDatabase(), new int[getNumExpressions()], new int[0], null);
} }
WindowFrame frame = over.getWindowFrame();
if (frame != null) {
int n = 0;
if (frame.getStarting().isVariable()) {
n++;
}
WindowFrameBound following = frame.getFollowing();
if (following != null && following.isVariable()) {
n++;
}
numFrameExpressions = n;
}
} }
return this; return this;
} }
...@@ -200,9 +220,12 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -200,9 +220,12 @@ public abstract class DataAnalysisOperation extends Expression {
/** /**
* Update a row of an aggregate. * Update a row of an aggregate.
* *
* @param session the database session * @param session
* @param groupData data for the aggregate group * the database session
* @param groupRowId row id of group * @param groupData
* data for the aggregate group
* @param groupRowId
* row id of group
*/ */
protected abstract void updateAggregate(Session session, SelectGroups groupData, int groupRowId); protected abstract void updateAggregate(Session session, SelectGroups groupData, int groupRowId);
...@@ -222,12 +245,21 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -222,12 +245,21 @@ public abstract class DataAnalysisOperation extends Expression {
} }
/** /**
* Returns the number of expressions, excluding FILTER and OVER clauses. * Returns the number of expressions, excluding OVER clause.
* *
* @return the number of expressions * @return the number of expressions
*/ */
protected abstract int getNumExpressions(); protected abstract int getNumExpressions();
/**
* Returns the number of window frame expressions.
*
* @return the number of window frame expressions
*/
private int getNumFrameExpressions() {
return numFrameExpressions;
}
/** /**
* Stores current values of expressions into the specified array. * Stores current values of expressions into the specified array.
* *
...@@ -241,9 +273,12 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -241,9 +273,12 @@ public abstract class DataAnalysisOperation extends Expression {
/** /**
* Get the aggregate data for a window clause. * Get the aggregate data for a window clause.
* *
* @param session database session * @param session
* @param groupData aggregate group data * database session
* @param forOrderBy true if this is for ORDER BY * @param groupData
* aggregate group data
* @param forOrderBy
* true if this is for ORDER BY
* @return the aggregate data object, specific to each kind of aggregate. * @return the aggregate data object, specific to each kind of aggregate.
*/ */
protected Object getWindowData(Session session, SelectGroups groupData, boolean forOrderBy) { protected Object getWindowData(Session session, SelectGroups groupData, boolean forOrderBy) {
...@@ -261,9 +296,12 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -261,9 +296,12 @@ public abstract class DataAnalysisOperation extends Expression {
/** /**
* Get the aggregate group data object from the collector object. * Get the aggregate group data object from the collector object.
* @param groupData the collector object *
* @param ifExists if true, return null if object not found, * @param groupData
* if false, return new object if nothing found * the collector object
* @param ifExists
* if true, return null if object not found, if false, return new
* object if nothing found
* @return group data object * @return group data object
*/ */
protected Object getGroupData(SelectGroups groupData, boolean ifExists) { protected Object getGroupData(SelectGroups groupData, boolean ifExists) {
...@@ -320,6 +358,20 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -320,6 +358,20 @@ public abstract class DataAnalysisOperation extends Expression {
: getWindowResult(session, groupData); : getWindowResult(session, groupData);
} }
/**
* Returns offset of window frame parameters.
*
* @return offset of window frame parameters
*/
protected final int getWindowFrameParametersOffset() {
int frameParametersOffset = getNumExpressions();
ArrayList<SelectOrderBy> orderBy = over.getOrderBy();
if (orderBy != null) {
frameParametersOffset += orderBy.size();
}
return frameParametersOffset;
}
/** /**
* Returns result of this window function or window aggregate. This method * Returns result of this window function or window aggregate. This method
* is not used for plain aggregates. * is not used for plain aggregates.
...@@ -374,22 +426,38 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -374,22 +426,38 @@ public abstract class DataAnalysisOperation extends Expression {
/** /**
* Update a row of an ordered aggregate. * Update a row of an ordered aggregate.
* *
* @param session the database session * @param session
* @param groupData data for the aggregate group * the database session
* @param groupRowId row id of group * @param groupData
* @param orderBy list of order by expressions * data for the aggregate group
* @param groupRowId
* row id of group
* @param orderBy
* list of order by expressions
*/ */
protected void updateOrderedAggregate(Session session, SelectGroups groupData, int groupRowId, protected void updateOrderedAggregate(Session session, SelectGroups groupData, int groupRowId,
ArrayList<SelectOrderBy> orderBy) { ArrayList<SelectOrderBy> orderBy) {
int ne = getNumExpressions(); int ne = getNumExpressions();
int size = orderBy != null ? orderBy.size() : 0; int size = orderBy != null ? orderBy.size() : 0;
Value[] array = new Value[ne + size + 1]; int frameSize = getNumFrameExpressions();
Value[] array = new Value[ne + size + frameSize + 1];
rememberExpressions(session, array); rememberExpressions(session, array);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
@SuppressWarnings("null") @SuppressWarnings("null")
SelectOrderBy o = orderBy.get(i); SelectOrderBy o = orderBy.get(i);
array[ne++] = o.expression.getValue(session); array[ne++] = o.expression.getValue(session);
} }
if (frameSize > 0) {
WindowFrame frame = over.getWindowFrame();
WindowFrameBound bound = frame.getStarting();
if (bound.isVariable()) {
array[ne++] = bound.getValue().getValue(session);
}
bound = frame.getFollowing();
if (bound != null && bound.isVariable()) {
array[ne++] = bound.getValue().getValue(session);
}
}
array[ne] = ValueInt.get(groupRowId); array[ne] = ValueInt.get(groupRowId);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
ArrayList<Value[]> data = (ArrayList<Value[]>) getWindowData(session, groupData, true); ArrayList<Value[]> data = (ArrayList<Value[]>) getWindowData(session, groupData, true);
...@@ -408,6 +476,7 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -408,6 +476,7 @@ public abstract class DataAnalysisOperation extends Expression {
rowIdColumn += orderBy.size(); rowIdColumn += orderBy.size();
Collections.sort(orderedData, overOrderBySort); Collections.sort(orderedData, overOrderBySort);
} }
rowIdColumn += getNumFrameExpressions();
getOrderedResultLoop(session, result, orderedData, rowIdColumn); getOrderedResultLoop(session, result, orderedData, rowIdColumn);
partition.setOrderedResult(result); partition.setOrderedResult(result);
} }
...@@ -433,7 +502,8 @@ public abstract class DataAnalysisOperation extends Expression { ...@@ -433,7 +502,8 @@ public abstract class DataAnalysisOperation extends Expression {
/** /**
* Used to create SQL for the OVER and FILTER clauses. * Used to create SQL for the OVER and FILTER clauses.
* *
* @param builder string builder * @param builder
* string builder
* @return the builder object * @return the builder object
*/ */
protected StringBuilder appendTailConditions(StringBuilder builder) { protected StringBuilder appendTailConditions(StringBuilder builder) {
......
...@@ -66,7 +66,7 @@ public final class Window { ...@@ -66,7 +66,7 @@ public final class Window {
* @param orderBy * @param orderBy
* ORDER BY clause, or null * ORDER BY clause, or null
* @param frame * @param frame
* window frame clause * window frame clause, or null
*/ */
public Window(String parent, ArrayList<Expression> partitionBy, ArrayList<SelectOrderBy> orderBy, public Window(String parent, ArrayList<Expression> partitionBy, ArrayList<SelectOrderBy> orderBy,
WindowFrame frame) { WindowFrame frame) {
...@@ -97,6 +97,9 @@ public final class Window { ...@@ -97,6 +97,9 @@ public final class Window {
o.expression.mapColumns(resolver, level, Expression.MAP_IN_WINDOW); o.expression.mapColumns(resolver, level, Expression.MAP_IN_WINDOW);
} }
} }
if (frame != null) {
frame.mapColumns(resolver, level, Expression.MAP_IN_WINDOW);
}
} }
private void resolveWindows(ColumnResolver resolver) { private void resolveWindows(ColumnResolver resolver) {
...@@ -136,6 +139,9 @@ public final class Window { ...@@ -136,6 +139,9 @@ public final class Window {
o.expression = o.expression.optimize(session); o.expression = o.expression.optimize(session);
} }
} }
if (frame != null) {
frame.optimize(session);
}
} }
/** /**
...@@ -253,6 +259,9 @@ public final class Window { ...@@ -253,6 +259,9 @@ public final class Window {
o.expression.updateAggregate(session, stage); o.expression.updateAggregate(session, stage);
} }
} }
if (frame != null) {
frame.updateAggregate(session, stage);
}
} }
@Override @Override
......
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
*/ */
package org.h2.expression.analysis; package org.h2.expression.analysis;
import org.h2.engine.Session;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.table.ColumnResolver;
/** /**
* Window frame bound. * Window frame bound.
...@@ -14,7 +16,9 @@ public class WindowFrameBound { ...@@ -14,7 +16,9 @@ public class WindowFrameBound {
private final WindowFrameBoundType type; private final WindowFrameBoundType type;
private final Expression value; private Expression value;
private boolean isVariable;
/** /**
* Creates new instance of window frame bound. * Creates new instance of window frame bound.
...@@ -51,6 +55,62 @@ public class WindowFrameBound { ...@@ -51,6 +55,62 @@ public class WindowFrameBound {
return value; return value;
} }
/**
* Returns whether bound is defined with a variable. This method may be used
* only after {@link #optimize(Session)} invocation.
*
* @return whether bound is defined with a variable
*/
public boolean isVariable() {
return isVariable;
}
/**
* Map the columns of the resolver to expression columns.
*
* @param resolver
* the column resolver
* @param level
* the subquery nesting level
* @param state
* current state for nesting checks
*/
void mapColumns(ColumnResolver resolver, int level, int state) {
if (value != null) {
value.mapColumns(resolver, level, state);
}
}
/**
* Try to optimize bound expression.
*
* @param session
* the session
*/
void optimize(Session session) {
if (value != null) {
value = value.optimize(session);
if (!value.isConstant()) {
isVariable = true;
}
}
}
/**
* Update an aggregate value.
*
* @param session
* the session
* @param stage
* select stage
* @see Expression#updateAggregate(Session, int)
*/
void updateAggregate(Session session, int stage) {
if (value != null) {
value.updateAggregate(session, stage);
}
}
/** /**
* Appends SQL representation to the specified builder. * Appends SQL representation to the specified builder.
* *
......
...@@ -349,18 +349,19 @@ public class WindowFunction extends DataAnalysisOperation { ...@@ -349,18 +349,19 @@ public class WindowFunction extends DataAnalysisOperation {
private void getNth(Session session, HashMap<Integer, Value> result, ArrayList<Value[]> ordered, int rowIdColumn) { private void getNth(Session session, HashMap<Integer, Value> result, ArrayList<Value[]> ordered, int rowIdColumn) {
int size = ordered.size(); int size = ordered.size();
int frameParametersOffset = getWindowFrameParametersOffset();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
Value[] row = ordered.get(i); Value[] row = ordered.get(i);
int rowId = row[rowIdColumn].getInt(); int rowId = row[rowIdColumn].getInt();
Value v; Value v;
switch (type) { switch (type) {
case FIRST_VALUE: case FIRST_VALUE:
v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, false), 0, v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
ignoreNulls); frameParametersOffset, i, false), 0, ignoreNulls);
break; break;
case LAST_VALUE: case LAST_VALUE:
v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, true), 0, v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
ignoreNulls); frameParametersOffset, i, true), 0, ignoreNulls);
break; break;
case NTH_VALUE: { case NTH_VALUE: {
int n = row[1].getInt(); int n = row[1].getInt();
...@@ -368,8 +369,8 @@ public class WindowFunction extends DataAnalysisOperation { ...@@ -368,8 +369,8 @@ public class WindowFunction extends DataAnalysisOperation {
throw DbException.getInvalidValueException("nth row", n); throw DbException.getInvalidValueException("nth row", n);
} }
n--; n--;
Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, Iterator<Value[]> iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(),
fromLast); frameParametersOffset, i, fromLast);
v = getNthValue(iter, n, ignoreNulls); v = getNthValue(iter, n, ignoreNulls);
break; break;
} }
......
...@@ -596,5 +596,23 @@ SELECT ID, VALUE, ...@@ -596,5 +596,23 @@ SELECT ID, VALUE,
> 8 4 [1, 2, 3, 4, 5, 6, 7, 8] [8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] > 8 4 [1, 2, 3, 4, 5, 6, 7, 8] [8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8]
> rows: 8 > rows: 8
SELECT ID, VALUE,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND VALUE FOLLOWING) RG,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID RANGE BETWEEN VALUE PRECEDING AND UNBOUNDED FOLLOWING) RGR,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND VALUE FOLLOWING) R,
ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID ROWS BETWEEN VALUE PRECEDING AND UNBOUNDED FOLLOWING) RR
FROM TEST;
> ID VALUE RG RGR R RR
> -- ----- ------------------------ ------------------------ ------------------------ ------------------------
> 1 1 [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8]
> 2 1 [1, 2, 3] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2, 3] [1, 2, 3, 4, 5, 6, 7, 8]
> 3 2 [1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8]
> 4 2 [1, 2, 3, 4, 5, 6] [2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [2, 3, 4, 5, 6, 7, 8]
> 5 3 [1, 2, 3, 4, 5, 6, 7, 8] [2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [2, 3, 4, 5, 6, 7, 8]
> 6 3 [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8]
> 7 4 [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8]
> 8 4 [1, 2, 3, 4, 5, 6, 7, 8] [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [4, 5, 6, 7, 8]
> rows: 8
DROP TABLE TEST; DROP TABLE TEST;
> ok > ok
...@@ -218,6 +218,28 @@ SELECT ID, CATEGORY, ...@@ -218,6 +218,28 @@ SELECT ID, CATEGORY,
> 13 4 1 1 1 2 3 4 > 13 4 1 1 1 2 3 4
> rows (ordered): 13 > rows (ordered): 13
SELECT ID, CATEGORY,
FIRST_VALUE(ID) OVER (ORDER BY ID ROWS BETWEEN CATEGORY FOLLOWING AND UNBOUNDED FOLLOWING) F,
LAST_VALUE(ID) OVER (ORDER BY ID ROWS BETWEEN CURRENT ROW AND CATEGORY FOLLOWING) L,
NTH_VALUE(ID, 2) OVER (ORDER BY ID ROWS BETWEEN CATEGORY FOLLOWING AND UNBOUNDED FOLLOWING) N
FROM TEST ORDER BY ID;
> ID CATEGORY F L N
> -- -------- ---- -- ----
> 1 1 2 2 3
> 2 1 3 3 4
> 3 1 4 4 5
> 4 1 5 5 6
> 5 1 6 6 7
> 6 1 7 7 8
> 7 2 9 9 10
> 8 2 10 10 11
> 9 3 12 12 13
> 10 3 13 13 null
> 11 3 null 13 null
> 12 4 null 13 null
> 13 4 null 13 null
> rows (ordered): 13
DROP TABLE TEST; DROP TABLE TEST;
> ok > ok
......
...@@ -805,4 +805,4 @@ queryparser tokenized freeze factorings recompilation unenclosed rfe dsync ...@@ -805,4 +805,4 @@ queryparser tokenized freeze factorings recompilation unenclosed rfe dsync
econd irst bcef ordinality nord unnest econd irst bcef ordinality nord unnest
analyst occupation distributive josaph aor engineer sajeewa isuru randil kevin doctor businessman artist ashan analyst occupation distributive josaph aor engineer sajeewa isuru randil kevin doctor businessman artist ashan
corrupts splitted disruption unintentional octets preconditions predicates subq objectweb insn opcodes corrupts splitted disruption unintentional octets preconditions predicates subq objectweb insn opcodes
preserves masking holder unboxing avert iae transformed subtle reevaluate exclusions subclause ftbl preserves masking holder unboxing avert iae transformed subtle reevaluate exclusions subclause ftbl rgr
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论