Skip to content

Commit f00a24c

Browse files
New 'UNLIST' function (#8418)
Implementation of the new internal function UNLIST
1 parent 730aa8f commit f00a24c

27 files changed

+1178
-20
lines changed

builds/win32/msvc15/engine_static.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
<ClCompile Include="..\..\..\src\jrd\recsrc\SingularStream.cpp" />
141141
<ClCompile Include="..\..\..\src\jrd\recsrc\SkipRowsStream.cpp" />
142142
<ClCompile Include="..\..\..\src\jrd\recsrc\SortedStream.cpp" />
143+
<ClCompile Include="..\..\..\src\jrd\recsrc\TableValueFunctionScan.cpp" />
143144
<ClCompile Include="..\..\..\src\jrd\recsrc\Union.cpp" />
144145
<ClCompile Include="..\..\..\src\jrd\recsrc\VirtualTableScan.cpp" />
145146
<ClCompile Include="..\..\..\src\jrd\recsrc\WindowedStream.cpp" />

builds/win32/msvc15/engine_static.vcxproj.filters

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@
111111
<ClCompile Include="..\..\..\src\jrd\recsrc\SortedStream.cpp">
112112
<Filter>JRD files\Data Access</Filter>
113113
</ClCompile>
114-
<ClCompile Include="..\..\..\src\jrd\recsrc\Union.cpp">
114+
<ClCompile Include="..\..\..\src\jrd\recsrc\Union.cpp">
115+
<Filter>JRD files\Data Access</Filter>
116+
</ClCompile>
117+
<ClCompile Include="..\..\..\src\jrd\recsrc\TableValueFunctionScan.cpp">
115118
<Filter>JRD files\Data Access</Filter>
116119
</ClCompile>
117120
<ClCompile Include="..\..\..\src\jrd\recsrc\VirtualTableScan.cpp">

doc/sql.extensions/README.unlist

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
SQL Language Extension: UNLIST
2+
3+
Function:
4+
The function parses the input string using the specified delimiter (comma "," is implied by default) and returns the identified substrings as discrete records containing a single field. Additionally, the desired type of the returned field can be specified. If the specified data type conversion is impossible, an error is raised at runtime.
5+
6+
Author:
7+
Chudaykin Alex <[email protected]>
8+
9+
Format
10+
<table value function> ::=
11+
UNLIST ( <input> [, <separator>] [, <data type conversion>] ) [AS] <correlation name> [ ( <derived column name> ) ]
12+
13+
<input> ::= <value>
14+
15+
<separator> ::= <value>
16+
17+
<data type conversion> ::= RETURNING <data type>
18+
19+
Syntax Rules:
20+
21+
1) <input>: any value expression that returns a string/blob of characters (or may be converted to a string), including string literals, table columns, constants, variables, expressions, etc. This parameter is mandatory.
22+
2) <separator>: optional value expression that returns a string which is used as a delimiter (i.e. it separates one value from another inside the input string). It may also be a BLOB TEXT value, limited to 32KB. If an empty string is specified, the output will be just one record containing the input string. If omitted, the comma character is used as a delimiter.
23+
2) <data type>: target data type to convert the output values into. Alternatively, a domain can be specified as the returned type. If omitted, VARCHAR(32) is implied. Feel free to suggest any better alternative default.
24+
3) <correlation name>: alias of the record set returned by the UNLIST function. It is a mandatory parameter (per SQL standard).
25+
4) <derived column name>: optional alias of the column returned by the UNLIST function. If omitted, UNLIST is used as an alias.
26+
27+
Example:
28+
A)
29+
SELECT * FROM UNLIST('1,2,3,4,5') AS U;
30+
31+
B)
32+
SELECT * FROM UNLIST('100:200:300:400:500', ':' RETURNING INT) AS U;
33+
34+
C)
35+
SELECT U.* FROM UNLIST(‘text1, text2, text3’) AS U;
36+
37+
D)
38+
SELECT C0 FROM UNLIST(‘text1, text2, text3’) AS U(C0);
39+
40+
E)
41+
SELECT U.C0 FROM UNLIST(‘text1, text2, text3’) AS U(C0);
42+
43+
F)
44+
SET TERM ^ ;
45+
RECREATE PROCEDURE TEST_PROC RETURNS (PROC_RETURN_INT INT)
46+
AS
47+
DECLARE VARIABLE text VARCHAR(11);
48+
BEGIN
49+
text = '123:123:123';
50+
FOR SELECT * FROM UNLIST( :text, ':' RETURNING INT) AS A INTO :PROC_RETURN_INT DO
51+
SUSPEND;
52+
END^
53+
SET TERM ; ^
54+
SELECT * FROM TEST_PROC;
55+
56+
G)
57+
CREATE DOMAIN D1 AS INT;
58+
SELECT TEST_DOMAIN FROM UNLIST('1,2,3,4' RETURNING D1) AS A(TEST_DOMAIN);
59+
60+
CREATE TABLE TABLE_TEST (COL1 INT);
61+
SELECT TEST_TYPE_OF FROM UNLIST('1,2,3,4' RETURNING TYPE OF COLUMN TABLE_TEST.COL1) AS A(TEST_TYPE_OF);
62+
H)
63+
CREATE VIEW TEST_VIEW AS SELECT * FROM UNLIST('1,2,3,4') AS A(B);
64+
SELECT B FROM TEST_VIEW;
65+
66+
Unacceptable behavior:
67+
SELECT UNLIST FROM UNLIST('UNLIST,A,S,A') AS A;
68+
69+
70+
71+

src/common/ParserTokens.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ PARSER_TOKEN(TOK_UNICODE_CHAR, "UNICODE_CHAR", true)
524524
PARSER_TOKEN(TOK_UNICODE_VAL, "UNICODE_VAL", true)
525525
PARSER_TOKEN(TOK_UNION, "UNION", false)
526526
PARSER_TOKEN(TOK_UNIQUE, "UNIQUE", false)
527+
PARSER_TOKEN(TOK_UNLIST, "UNLIST", true)
527528
PARSER_TOKEN(TOK_UNKNOWN, "UNKNOWN", false)
528529
PARSER_TOKEN(TOK_UPDATE, "UPDATE", false)
529530
PARSER_TOKEN(TOK_UPDATING, "UPDATING", false)

src/dsql/DdlNodes.epp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9119,6 +9119,14 @@ void CreateAlterViewNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScra
91199119
else
91209120
updatable = false;
91219121

9122+
9123+
if (field && context && (context->ctx_flags & CTX_blr_fields))
9124+
{
9125+
field = nullptr;
9126+
context = nullptr;
9127+
updatable = false;
9128+
}
9129+
91229130
// If this is an expression, check to make sure there is a name specified.
91239131

91249132
if (!ptr && !fieldStr)

src/dsql/DsqlCompilerScratch.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,11 @@ bool DsqlCompilerScratch::pass1RelProcIsRecursive(RecordSourceNode* input)
945945
relName = relNode->dsqlName;
946946
relAlias = relNode->alias;
947947
}
948+
else if (auto tableValueFunctionNode = nodeAs<TableValueFunctionSourceNode>(input))
949+
{
950+
relName = tableValueFunctionNode->dsqlName;
951+
relAlias = tableValueFunctionNode->alias.c_str();
952+
}
948953
//// TODO: LocalTableSourceNode
949954
else
950955
return false;

src/dsql/ExprNodes.cpp

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6027,6 +6027,11 @@ DmlNode* FieldNode::parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* cs
60276027
Arg::Str(name) << Arg::Str(procedure->getName().toString()));
60286028
}
60296029
}
6030+
else if (tail->csb_table_value_fun)
6031+
{
6032+
csb->csb_blr_reader.getMetaName(name);
6033+
id = tail->csb_table_value_fun->getId(name);
6034+
}
60306035
else
60316036
{
60326037
jrd_rel* relation = tail->csb_relation;
@@ -6250,6 +6255,13 @@ ValueExprNode* FieldNode::internalDsqlPass(DsqlCompilerScratch* dsqlScratch, Rec
62506255
procNode->dsqlContext = stackContext;
62516256
*list = procNode;
62526257
}
6258+
else if (context->ctx_table_value_fun)
6259+
{
6260+
auto tableValueFunctionNode = FB_NEW_POOL(*tdbb->getDefaultPool())
6261+
TableValueFunctionSourceNode(*tdbb->getDefaultPool());
6262+
tableValueFunctionNode->dsqlContext = stackContext;
6263+
*list = tableValueFunctionNode;
6264+
}
62536265
//// TODO: LocalTableSourceNode
62546266

62556267
fb_assert(*list);
@@ -6446,9 +6458,28 @@ dsql_fld* FieldNode::resolveContext(DsqlCompilerScratch* dsqlScratch, const Meta
64466458
return nullptr;
64476459
}
64486460

6461+
const TEXT* dsqlName = nullptr;
6462+
dsql_fld* outputField = nullptr;
64496463
dsql_rel* relation = context->ctx_relation;
64506464
dsql_prc* procedure = context->ctx_procedure;
6451-
if (!relation && !procedure)
6465+
dsql_tab_func* tableValueFunctionContext = context->ctx_table_value_fun;
6466+
6467+
if (relation)
6468+
{
6469+
dsqlName = relation->rel_name.c_str();
6470+
outputField = relation->rel_fields;
6471+
}
6472+
else if (procedure)
6473+
{
6474+
dsqlName = procedure->prc_name.identifier.c_str();
6475+
outputField = procedure->prc_outputs;
6476+
}
6477+
else if (tableValueFunctionContext)
6478+
{
6479+
dsqlName = tableValueFunctionContext->funName.c_str();
6480+
outputField = tableValueFunctionContext->outputField;
6481+
}
6482+
else
64526483
return nullptr;
64536484

64546485
// if there is no qualifier, then we cannot match against
@@ -6491,7 +6522,9 @@ dsql_fld* FieldNode::resolveContext(DsqlCompilerScratch* dsqlScratch, const Meta
64916522
}
64926523

64936524
if (aliasName.isEmpty())
6494-
aliasName = relation ? relation->rel_name : procedure->prc_name.identifier;
6525+
{
6526+
aliasName = dsqlName;
6527+
}
64956528

64966529
fb_assert(aliasName.hasData());
64976530

@@ -6501,7 +6534,7 @@ dsql_fld* FieldNode::resolveContext(DsqlCompilerScratch* dsqlScratch, const Meta
65016534

65026535
// Lookup field in relation or procedure
65036536

6504-
return relation ? relation->rel_fields : procedure->prc_outputs;
6537+
return outputField;
65056538
}
65066539

65076540
bool FieldNode::dsqlAggregateFinder(AggregateFinder& visitor)

src/dsql/Nodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,7 @@ class ExprNode : public DmlNode
521521
TYPE_SELECT_EXPR,
522522
TYPE_UNION,
523523
TYPE_WINDOW,
524+
TYPE_TABLE_VALUE_FUNCTION,
524525

525526
// List types
526527
TYPE_REC_SOURCE_LIST,

src/dsql/StmtNodes.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10682,6 +10682,8 @@ static dsql_ctx* dsqlGetContext(const RecordSourceNode* node)
1068210682
return procNode->dsqlContext;
1068310683
else if (auto relNode = nodeAs<RelationSourceNode>(node))
1068410684
return relNode->dsqlContext;
10685+
else if (auto tableValueFunctionNode = nodeAs<TableValueFunctionSourceNode>(node))
10686+
return tableValueFunctionNode->dsqlContext;
1068510687
//// TODO: LocalTableSourceNode
1068610688
else if (auto rseNode = nodeAs<RseNode>(node))
1068710689
return rseNode->dsqlContext;
@@ -10699,6 +10701,8 @@ static void dsqlGetContexts(DsqlContextStack& contexts, const RecordSourceNode*
1069910701
contexts.push(procNode->dsqlContext);
1070010702
else if (auto relNode = nodeAs<RelationSourceNode>(node))
1070110703
contexts.push(relNode->dsqlContext);
10704+
else if (auto tableValueFunctionNode = nodeAs<TableValueFunctionSourceNode>(node))
10705+
contexts.push(tableValueFunctionNode->dsqlContext);
1070210706
//// TODO: LocalTableSourceNode
1070310707
else if (auto rseNode = nodeAs<RseNode>(node))
1070410708
{

src/dsql/dsql.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,21 @@ class dsql_intlsym : public pool_alloc<dsql_type_intlsym>
410410
USHORT intlsym_bytes_per_char = 0;
411411
};
412412

413+
414+
// Table value function
415+
class dsql_tab_func : public pool_alloc<dsql_type_tab_func>
416+
{
417+
public:
418+
explicit dsql_tab_func(MemoryPool& p)
419+
: funName(p),
420+
outputField(nullptr)
421+
{
422+
}
423+
424+
MetaName funName; // Name of function
425+
dsql_fld* outputField; // Output parameters
426+
};
427+
413428
// values used in intlsym_flags
414429

415430
enum intlsym_flags_vals {
@@ -454,6 +469,7 @@ class dsql_ctx : public pool_alloc<dsql_type_ctx>
454469

455470
dsql_rel* ctx_relation = nullptr; // Relation for context
456471
dsql_prc* ctx_procedure = nullptr; // Procedure for context
472+
dsql_tab_func* ctx_table_value_fun = nullptr; // Table value function context
457473
NestConst<ValueListNode> ctx_proc_inputs; // Procedure input parameters
458474
dsql_map* ctx_map = nullptr; // Maps for aggregates and unions
459475
RseNode* ctx_rse = nullptr; // Sub-rse for aggregates
@@ -475,6 +491,7 @@ class dsql_ctx : public pool_alloc<dsql_type_ctx>
475491
{
476492
ctx_relation = v.ctx_relation;
477493
ctx_procedure = v.ctx_procedure;
494+
ctx_table_value_fun = v.ctx_table_value_fun;
478495
ctx_proc_inputs = v.ctx_proc_inputs;
479496
ctx_map = v.ctx_map;
480497
ctx_rse = v.ctx_rse;
@@ -518,6 +535,7 @@ const USHORT CTX_view_with_check_store = 0x20; // Context of WITH CHECK OPTION
518535
const USHORT CTX_view_with_check_modify = 0x40; // Context of WITH CHECK OPTION view's modify trigger
519536
const USHORT CTX_cursor = 0x80; // Context is a cursor
520537
const USHORT CTX_lateral = 0x100; // Context is a lateral derived table
538+
const USHORT CTX_blr_fields = 0x200; // Fields of the context are defined inside BLR
521539

522540
//! Aggregate/union map block to map virtual fields to their base
523541
//! TMN: NOTE! This datatype should definitely be renamed!

src/dsql/make.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,42 @@ FieldNode* MAKE_field(dsql_ctx* context, dsql_fld* field, ValueListNode* indices
423423
}
424424

425425

426+
/**
427+
428+
MAKE_field
429+
430+
@brief Make up a dsql_fld from descriptor.
431+
432+
433+
@param field
434+
@param desc
435+
436+
**/
437+
void MAKE_field(dsql_fld* field, const dsc* desc)
438+
{
439+
DEV_BLKCHK(field, dsql_type_fld);
440+
441+
field->dtype = desc->dsc_dtype;
442+
field->scale = desc->dsc_scale;
443+
field->subType = desc->dsc_sub_type;
444+
field->length = desc->dsc_length;
445+
446+
if (desc->dsc_dtype <= dtype_any_text)
447+
{
448+
field->collationId = DSC_GET_COLLATE(desc);
449+
field->charSetId = DSC_GET_CHARSET(desc);
450+
}
451+
else if (desc->dsc_dtype == dtype_blob)
452+
{
453+
field->charSetId = desc->dsc_scale;
454+
field->collationId = desc->dsc_flags >> 8;
455+
}
456+
457+
if (desc->dsc_flags & DSC_nullable)
458+
field->flags |= FLD_nullable;
459+
}
460+
461+
426462
/**
427463
428464
MAKE_field_name

src/dsql/make_proto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Jrd::LiteralNode* MAKE_const_sint64(SINT64 value, SCHAR scale);
8484
Jrd::ValueExprNode* MAKE_constant(const char*, Jrd::dsql_constant_type, SSHORT = 0);
8585
Jrd::LiteralNode* MAKE_str_constant(const Jrd::IntlString*, SSHORT);
8686
Jrd::FieldNode* MAKE_field(Jrd::dsql_ctx*, Jrd::dsql_fld*, Jrd::ValueListNode*);
87+
void MAKE_field(Jrd::dsql_fld*, const dsc*);
8788
Jrd::FieldNode* MAKE_field_name(const char*);
8889
Jrd::dsql_par* MAKE_parameter(Jrd::dsql_msg*, bool, bool, USHORT, const Jrd::ValueExprNode*);
8990
void MAKE_parameter_names(Jrd::dsql_par*, const Jrd::ValueExprNode*);

src/dsql/parse-conflicts.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
117 shift/reduce conflicts, 22 reduce/reduce conflicts.
1+
118 shift/reduce conflicts, 22 reduce/reduce conflicts.

0 commit comments

Comments
 (0)