diff --git a/doc/sql.extensions/README.unlist b/doc/sql.extensions/README.unlist index 969c3072622..1cab515be50 100644 --- a/doc/sql.extensions/README.unlist +++ b/doc/sql.extensions/README.unlist @@ -74,5 +74,45 @@ Unacceptable behavior: SELECT UNLIST FROM UNLIST('UNLIST,A,S,A') AS A; +Short syntax for UNLIST with IN operator: + UNLIST can be used directly with the IN operator without requiring a full subquery. + Instead of writing IN (SELECT * FROM UNLIST(...) AS U), you can use the shorter syntax IN UNLIST(...). + +Syntax: + ::= + [NOT] IN + +
::= + UNLIST ( [, ] [, ] ) + +Examples: + +A) + SELECT * FROM EMPLOYEE WHERE EMP_NO IN UNLIST('2,4,5,7,11'); +B) + SELECT * FROM EMPLOYEE WHERE JOB_COUNTRY IN UNLIST('France,Italy,England'); +C) + SELECT EMP_NO FROM EMPLOYEE WHERE JOB_COUNTRY NOT IN UNLIST('USA'); +D) + SELECT * FROM EMPLOYEE WHERE DEPT_NO IN UNLIST('100:110:115:120', ':' RETURNING INT); +E) + SET AUTOTERM; + RECREATE PROCEDURE GET_EMPLOYEES_BY_PHONE_EXT (PHONE VARCHAR(1000)) + RETURNS (EMPLOYEE_NAME VARCHAR(100)) + AS + BEGIN + FOR + SELECT FIRST_NAME FROM EMPLOYEE + WHERE PHONE_EXT IN UNLIST(:PHONE, ',' RETURNING INT) + INTO :EMPLOYEE_NAME + DO + SUSPEND; + END; + + SELECT A.EMPLOYEE_NAME FROM GET_EMPLOYEES_BY_PHONE_EXT('2,3,7') AS A; + +Note: + The short syntax automatically provides the correlation name and column name, + so they cannot be specified explicitly when using this form. diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 056d36642de..61363c34c85 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -7801,6 +7801,39 @@ in_predicate ComparativeBoolNode::DFLAG_ANSI_ANY, $4); $$ = newNode(node); } + | value IN table_value_function_unlist_short(NOTRIAL($1)) + { + $$ = newNode(blr_eql, $1, + ComparativeBoolNode::DFLAG_ANSI_ANY, $3); + } + | value NOT IN table_value_function_unlist_short(NOTRIAL($1)) + { + const auto node = newNode(blr_eql, $1, + ComparativeBoolNode::DFLAG_ANSI_ANY, $4); + $$ = newNode(node); + } + ; + +%type table_value_function_unlist_short() +table_value_function_unlist_short($autoTypeFromValue) + : table_value_function_unlist + { + const auto unlistNode = nodeAs($1); + unlistNode->alias = UnlistFunctionSourceNode::FUNC_NAME; + unlistNode->shortEntry = true; + + if (unlistNode->dsqlField == nullptr) + unlistNode->dsqlAutoTypeFromValue = $autoTypeFromValue; + + const auto rseNode = newNode(); + rseNode->dsqlFlags |= RecordSourceNode::DFLAG_BODY_WRAPPER; + rseNode->dsqlFrom = newNode(unlistNode); + + const auto selectNode = newNode(); + selectNode->querySpec = rseNode; + + $$ = selectNode; + } ; %type exists_predicate diff --git a/src/jrd/RecordSourceNodes.cpp b/src/jrd/RecordSourceNodes.cpp index 764effa6f47..ab1f08095c7 100644 --- a/src/jrd/RecordSourceNodes.cpp +++ b/src/jrd/RecordSourceNodes.cpp @@ -4380,14 +4380,22 @@ dsql_fld* UnlistFunctionSourceNode::makeField(DsqlCompilerScratch* dsqlScratch) field = newField; dsc desc; - DsqlDescMaker::fromNode(dsqlScratch, &desc, inputItem); - auto ttype = desc.getCharSet(); + if (dsqlAutoTypeFromValue && shortEntry) + { + dsqlAutoTypeFromValue = Node::doDsqlPass(dsqlScratch, dsqlAutoTypeFromValue, false); + DsqlDescMaker::fromNode(dsqlScratch, &desc, dsqlAutoTypeFromValue); + } + else + { + DsqlDescMaker::fromNode(dsqlScratch, &desc, inputItem); + auto ttype = desc.getCharSet(); - if (ttype == CS_NONE && !desc.isText() && !desc.isBlob()) - ttype = CS_ASCII; + if (ttype == CS_NONE && !desc.isText() && !desc.isBlob()) + ttype = CS_ASCII; - const auto bytesPerChar = DSqlDataTypeUtil(dsqlScratch).maxBytesPerChar(ttype); - desc.makeText(bytesPerChar * DEFAULT_UNLIST_TEXT_LENGTH, ttype); + const auto bytesPerChar = DSqlDataTypeUtil(dsqlScratch).maxBytesPerChar(ttype); + desc.makeVarying(bytesPerChar * DEFAULT_UNLIST_TEXT_LENGTH, ttype); + } MAKE_field(newField, &desc); newField->fld_id = 0; } diff --git a/src/jrd/RecordSourceNodes.h b/src/jrd/RecordSourceNodes.h index bb2f0d66236..a9f9c470523 100644 --- a/src/jrd/RecordSourceNodes.h +++ b/src/jrd/RecordSourceNodes.h @@ -1056,12 +1056,12 @@ class TableValueFunctionSourceNode class UnlistFunctionSourceNode : public TableValueFunctionSourceNode { public: - explicit UnlistFunctionSourceNode(MemoryPool& pool) : TableValueFunctionSourceNode(pool) + explicit UnlistFunctionSourceNode(MemoryPool& pool) : TableValueFunctionSourceNode(pool) { } RecordSource* compile(thread_db* tdbb, Optimizer* opt, bool innerSubStream) final; - dsql_fld* makeField(DsqlCompilerScratch* dsqlScratch) final; + dsql_fld* makeField(DsqlCompilerScratch* dsqlScratch) override; static constexpr char const* FUNC_NAME = "UNLIST"; static constexpr USHORT DEFAULT_UNLIST_TEXT_LENGTH = 32; @@ -1070,6 +1070,10 @@ class UnlistFunctionSourceNode : public TableValueFunctionSourceNode { return FUNC_NAME; } + +public: + NestConst dsqlAutoTypeFromValue; + bool shortEntry = false; }; class GenSeriesFunctionSourceNode final : public TableValueFunctionSourceNode