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

Add UNNEST table function

上级 f33de19b
...@@ -5215,6 +5215,18 @@ The method returns a value with the same data type as the first parameter. ...@@ -5215,6 +5215,18 @@ The method returns a value with the same data type as the first parameter.
CALL TRUNCATE_VALUE(X, 10, TRUE); CALL TRUNCATE_VALUE(X, 10, TRUE);
" "
"Functions (System)","UNNEST","
UNNEST(array, [,...]) [WITH ORDINALITY]
","
Returns the result set.
Number of columns is equal to number of arguments,
plus one additional column with row number if WITH ORDINALITY is specified.
Number of rows is equal to length of longest specified array.
If multiple arguments are specified and they have different length, cells with missing values will contain null values.
","
SELECT * FROM UNNEST(ARRAY['a', 'b', 'c']);
"
"Functions (System)","USER"," "Functions (System)","USER","
{ USER | CURRENT_USER } () { USER | CURRENT_USER } ()
"," ","
......
...@@ -3406,6 +3406,23 @@ public class Parser { ...@@ -3406,6 +3406,23 @@ public class Parser {
tf.setColumns(columns); tf.setColumns(columns);
break; break;
} }
case Function.UNNEST: {
ArrayList<Column> columns = Utils.newSmallArrayList();
if (!readIf(CLOSE_PAREN)) {
int i = 0;
do {
function.setParameter(i++, readExpression());
columns.add(new Column("C" + i, Value.NULL));
} while (readIfMore(true));
}
if (readIf(WITH)) {
read("ORDINALITY");
columns.add(new Column("NORD", Value.INT));
}
TableFunction tf = (TableFunction) function;
tf.setColumns(columns);
break;
}
default: default:
if (!readIf(CLOSE_PAREN)) { if (!readIf(CLOSE_PAREN)) {
int i = 0; int i = 0;
......
...@@ -134,7 +134,8 @@ public class Function extends Expression implements FunctionCall { ...@@ -134,7 +134,8 @@ public class Function extends Expression implements FunctionCall {
ARRAY_LENGTH = 217, LINK_SCHEMA = 218, GREATEST = 219, LEAST = 220, ARRAY_LENGTH = 217, LINK_SCHEMA = 218, GREATEST = 219, LEAST = 220,
CANCEL_SESSION = 221, SET = 222, TABLE = 223, TABLE_DISTINCT = 224, CANCEL_SESSION = 221, SET = 222, TABLE = 223, TABLE_DISTINCT = 224,
FILE_READ = 225, TRANSACTION_ID = 226, TRUNCATE_VALUE = 227, FILE_READ = 225, TRANSACTION_ID = 226, TRUNCATE_VALUE = 227,
NVL2 = 228, DECODE = 229, ARRAY_CONTAINS = 230, FILE_WRITE = 232; NVL2 = 228, DECODE = 229, ARRAY_CONTAINS = 230, FILE_WRITE = 232,
UNNEST = 233;
public static final int REGEXP_LIKE = 240; public static final int REGEXP_LIKE = 240;
...@@ -459,10 +460,9 @@ public class Function extends Expression implements FunctionCall { ...@@ -459,10 +460,9 @@ public class Function extends Expression implements FunctionCall {
addFunction("H2VERSION", H2VERSION, 0, Value.STRING); addFunction("H2VERSION", H2VERSION, 0, Value.STRING);
// TableFunction // TableFunction
addFunctionWithNull("TABLE", TABLE, addFunctionWithNull("TABLE", TABLE, VAR_ARGS, Value.RESULT_SET);
VAR_ARGS, Value.RESULT_SET); addFunctionWithNull("TABLE_DISTINCT", TABLE_DISTINCT, VAR_ARGS, Value.RESULT_SET);
addFunctionWithNull("TABLE_DISTINCT", TABLE_DISTINCT, addFunctionWithNull("UNNEST", UNNEST, VAR_ARGS, Value.RESULT_SET);
VAR_ARGS, Value.RESULT_SET);
// ON DUPLICATE KEY VALUES function // ON DUPLICATE KEY VALUES function
addFunction("VALUES", VALUES, 1, Value.NULL, false, true, false, true); addFunction("VALUES", VALUES, 1, Value.NULL, false, true, false, true);
...@@ -538,6 +538,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -538,6 +538,7 @@ public class Function extends Expression implements FunctionCall {
switch (info.type) { switch (info.type) {
case TABLE: case TABLE:
case TABLE_DISTINCT: case TABLE_DISTINCT:
case UNNEST:
return new TableFunction(database, info, Long.MAX_VALUE); return new TableFunction(database, info, Long.MAX_VALUE);
default: default:
return new Function(database, info); return new Function(database, info);
......
...@@ -17,6 +17,7 @@ import org.h2.result.LocalResult; ...@@ -17,6 +17,7 @@ import org.h2.result.LocalResult;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueArray; import org.h2.value.ValueArray;
import org.h2.value.ValueInt;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
import org.h2.value.ValueResultSet; import org.h2.value.ValueResultSet;
...@@ -24,19 +25,17 @@ import org.h2.value.ValueResultSet; ...@@ -24,19 +25,17 @@ import org.h2.value.ValueResultSet;
* Implementation of the functions TABLE(..) and TABLE_DISTINCT(..). * Implementation of the functions TABLE(..) and TABLE_DISTINCT(..).
*/ */
public class TableFunction extends Function { public class TableFunction extends Function {
private final boolean distinct;
private final long rowCount; private final long rowCount;
private Column[] columnList; private Column[] columns;
TableFunction(Database database, FunctionInfo info, long rowCount) { TableFunction(Database database, FunctionInfo info, long rowCount) {
super(database, info); super(database, info);
distinct = info.type == Function.TABLE_DISTINCT;
this.rowCount = rowCount; this.rowCount = rowCount;
} }
@Override @Override
public Value getValue(Session session) { public Value getValue(Session session) {
return getTable(session, args, false, distinct); return getTable(session, false);
} }
@Override @Override
...@@ -48,52 +47,60 @@ public class TableFunction extends Function { ...@@ -48,52 +47,60 @@ public class TableFunction extends Function {
@Override @Override
public StringBuilder getSQL(StringBuilder builder) { public StringBuilder getSQL(StringBuilder builder) {
if (info.type == UNNEST) {
super.getSQL(builder);
if (args.length < columns.length) {
builder.append(" WITH ORDINALITY");
}
return builder;
}
builder.append(getName()).append('('); builder.append(getName()).append('(');
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
if (i > 0) { if (i > 0) {
builder.append(", "); builder.append(", ");
} }
builder.append(columnList[i].getCreateSQL()).append('='); builder.append(columns[i].getCreateSQL()).append('=');
args[i].getSQL(builder); args[i].getSQL(builder);
} }
return builder.append(')'); return builder.append(')');
} }
@Override
public String getName() {
return distinct ? "TABLE_DISTINCT" : "TABLE";
}
@Override @Override
public ValueResultSet getValueForColumnList(Session session, public ValueResultSet getValueForColumnList(Session session,
Expression[] nullArgs) { Expression[] nullArgs) {
return getTable(session, args, true, false); return getTable(session, true);
} }
public void setColumns(ArrayList<Column> columns) { public void setColumns(ArrayList<Column> columns) {
this.columnList = columns.toArray(new Column[0]); this.columns = columns.toArray(new Column[0]);
} }
private ValueResultSet getTable(Session session, Expression[] argList, private ValueResultSet getTable(Session session, boolean onlyColumnList) {
boolean onlyColumnList, boolean distinctRows) { int totalColumns = columns.length;
int len = columnList.length; Expression[] header = new Expression[totalColumns];
Expression[] header = new Expression[len];
Database db = session.getDatabase(); Database db = session.getDatabase();
for (int i = 0; i < len; i++) { for (int i = 0; i < totalColumns; i++) {
Column c = columnList[i]; Column c = columns[i];
ExpressionColumn col = new ExpressionColumn(db, c); ExpressionColumn col = new ExpressionColumn(db, c);
header[i] = col; header[i] = col;
} }
LocalResult result = db.getResultFactory().create(session, header, len); LocalResult result = db.getResultFactory().create(session, header, totalColumns);
if (distinctRows) { if (!onlyColumnList && info.type == TABLE_DISTINCT) {
result.setDistinct(); result.setDistinct();
} }
if (!onlyColumnList) { if (!onlyColumnList) {
int len = totalColumns;
boolean unnest = info.type == UNNEST, addNumber = false;
if (unnest) {
len = args.length;
if (len < totalColumns) {
addNumber = true;
}
}
Value[][] list = new Value[len][]; Value[][] list = new Value[len][];
int rows = 0; int rows = 0;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
Value v = argList[i].getValue(session); Value v = args[i].getValue(session);
if (v == ValueNull.INSTANCE) { if (v == ValueNull.INSTANCE) {
list[i] = new Value[0]; list[i] = new Value[0];
} else { } else {
...@@ -104,21 +111,25 @@ public class TableFunction extends Function { ...@@ -104,21 +111,25 @@ public class TableFunction extends Function {
} }
} }
for (int row = 0; row < rows; row++) { for (int row = 0; row < rows; row++) {
Value[] r = new Value[len]; Value[] r = new Value[totalColumns];
for (int j = 0; j < len; j++) { for (int j = 0; j < len; j++) {
Value[] l = list[j]; Value[] l = list[j];
Value v; Value v;
if (l.length <= row) { if (l.length <= row) {
v = ValueNull.INSTANCE; v = ValueNull.INSTANCE;
} else { } else {
Column c = columnList[j]; Column c = columns[j];
v = l[row]; v = l[row];
v = c.convert(v); if (!unnest) {
v = v.convertPrecision(c.getPrecision(), false); v = c.convert(v).convertPrecision(c.getPrecision(), false)
v = v.convertScale(true, c.getScale()); .convertScale(true, c.getScale());
}
} }
r[j] = v; r[j] = v;
} }
if (addNumber) {
r[len] = ValueInt.get(row + 1);
}
result.addRow(r); result.addRow(r);
} }
} }
...@@ -132,7 +143,7 @@ public class TableFunction extends Function { ...@@ -132,7 +143,7 @@ public class TableFunction extends Function {
@Override @Override
public Expression[] getExpressionColumns(Session session) { public Expression[] getExpressionColumns(Session session) {
return getExpressionColumns(session, getTable(session, getArgs(), true, false).getResult()); return getExpressionColumns(session, getValueForColumnList(session, null).getResult());
} }
} }
...@@ -194,7 +194,7 @@ public class TestScript extends TestDb { ...@@ -194,7 +194,7 @@ public class TestScript extends TestDb {
"ifnull", "least", "link-schema", "lock-mode", "lock-timeout", "ifnull", "least", "link-schema", "lock-mode", "lock-timeout",
"memory-free", "memory-used", "nextval", "nullif", "nvl2", "memory-free", "memory-used", "nextval", "nullif", "nvl2",
"readonly", "rownum", "schema", "scope-identity", "session-id", "readonly", "rownum", "schema", "scope-identity", "session-id",
"set", "table", "transaction-id", "truncate-value", "user" }) { "set", "table", "transaction-id", "truncate-value", "unnest", "user" }) {
testScript("functions/system/" + s + ".sql"); testScript("functions/system/" + s + ".sql");
} }
for (String s : new String[] { "add_months", "current_date", "current_timestamp", for (String s : new String[] { "add_months", "current_date", "current_timestamp",
......
-- Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
SELECT * FROM UNNEST();
> exception INVALID_PARAMETER_COUNT_2
SELECT * FROM UNNEST(ARRAY[]);
> C1
> --
> rows: 0
SELECT * FROM UNNEST(ARRAY[1, 2, 3]);
> C1
> --
> 1
> 2
> 3
> rows: 3
SELECT * FROM UNNEST(ARRAY[1], ARRAY[2, 3, 4], ARRAY[5, 6]);
> C1 C2 C3
> ---- -- ----
> 1 2 5
> null 3 6
> null 4 null
> rows: 3
SELECT * FROM UNNEST(ARRAY[1], ARRAY[2, 3, 4], ARRAY[5, 6]) WITH ORDINALITY;
> C1 C2 C3 NORD
> ---- -- ---- ----
> 1 2 5 1
> null 3 6 2
> null 4 null 3
> rows: 3
EXPLAIN SELECT * FROM UNNEST(ARRAY[1]);
>> SELECT UNNEST.C1 FROM UNNEST((1,)) /* function */
EXPLAIN SELECT * FROM UNNEST(ARRAY[1]) WITH ORDINALITY;
>> SELECT UNNEST.C1, UNNEST.NORD FROM UNNEST((1,)) WITH ORDINALITY /* function */
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论