On 22-8-2014 16:54, Mark Rotteveel wrote:
Are there any objections if I try to come up with a patch to add OFFSET
and FETCH to the select syntax (CORE-4526)?

Attached you will find my patch for this feature.

The grammar matches the SQL definition. I have made the existing rows_clause required so it doesn't cause conflicts with the new select_expr alternative that uses the result_offset_clause and fetch_first_clause. For the delete_searched and update_searched I added a rows_clause_optional so they continue to work as is.

I have reused the existing RowsClause node. I was tempted to rename it to OffsetFetchClause as the implementation actually better matches the needs for offset/fetch than for rows, but I left it as is.

I also updated some of the error messages and included a readme file.

Let me know if I can commit this, or if things need to be further improved.

Mark
--
Mark Rotteveel
Index: doc/sql.extensions/README.offset_fetch.txt
===================================================================
--- doc/sql.extensions/README.offset_fetch.txt  (revision 0)
+++ doc/sql.extensions/README.offset_fetch.txt  (working copy)
@@ -0,0 +1,51 @@
+-----------------------
+OFFSET and FETCH clause
+-----------------------
+
+  Function:
+    SQL:2008 compliant equivalent for FIRST/SKIP. The OFFSET clause specifies
+    the number of rows to skip. The FETCH clause specifies the number of rows 
to
+    fetch.
+    
+    OFFSET and FETCH can be applied independently on top-level and nested query
+    expressions.
+    
+  Author:
+    Mark Rotteveel <[email protected]>
+    
+  Syntax rules:
+    SELECT ... [ ORDER BY <expr_list> ] 
+      [ OFFSET <simple_value_expr> { ROW | ROWS } ]
+      [ FETCH { FIRST | NEXT } [ <simple_value_expr> ] { ROW | ROWS } ONLY ]
+    
+    Where <simple_value_expr> is a (numeric) literal, a SQL parameter (?) or 
+    PSQL named parameter (:namedparameter).
+  
+  Scope:
+    DSQL, PSQL
+  
+  Example(s):
+    1. SELECT * FROM T1
+       ORDER BY COL1
+       OFFSET 10 ROWS
+    2. SELECT * FROM T1
+       ORDER BY COL1
+       FETCH FIRST 10 ROWS ONLY
+    3. SELECT * FROM (
+          SELECT * FROM T1
+          ORDER BY COL1 DESC
+          OFFSET 1 ROW
+          FETCH NEXT 10 ROWS ONLY
+       ) a
+       ORDER BY a.COL1
+       FETCH FIRST ROW ONLY
+  
+  Note(s):
+    1. Firebird doesn't support the percentage FETCH defined in the SQL 
+       standard.
+    2. Firebird doesn't support the FETCH ... WITH TIES defined in the SQL
+       standard.
+    3. The FIRST/SKIP and ROWS clause should be considered deprecated.
+    4. The OFFSET and/or FETCH clauses cannot be combined with ROWS or 
+       FIRST/SKIP on the same query expression.
+    5. Expressions, column references etc are not allowed within both clauses.
\ No newline at end of file
Index: doc/sql.extensions/README.offset_fetch.txt
===================================================================
--- doc/sql.extensions/README.offset_fetch.txt  (revision 0)
+++ doc/sql.extensions/README.offset_fetch.txt  (working copy)

Property changes on: doc/sql.extensions/README.offset_fetch.txt
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: doc/sql.extensions/README.rows
===================================================================
--- doc/sql.extensions/README.rows      (revision 60178)
+++ doc/sql.extensions/README.rows      (working copy)
@@ -35,3 +35,5 @@
        both <expr1> and <expr2> are used, then ROWS <expr1> TO <expr2> means:
        FIRST (<expr2> - <expr1> + 1) SKIP (<expr1> - 1). Note that there's no 
semantical equivalent
        for a SKIP clause used without a FIRST clause.
+    3. For SELECT the ROWS clause should be considered deprecated in favor of 
the SQL-standard 
+       OFFSET .. FETCH .. clauses
Index: src/dsql/parse.y
===================================================================
--- src/dsql/parse.y    (revision 60178)
+++ src/dsql/parse.y    (working copy)
@@ -570,6 +570,8 @@
 %token <metaNamePtr> SERVERWIDE
 %token <metaNamePtr> INCREMENT
 %token <metaNamePtr> TRUSTED
+%token <metaNamePtr> ROW
+%token <metaNamePtr> OFFSET
 
 // precedence declarations for expression evaluation
 
@@ -4893,6 +4895,21 @@
                        node->rowsClause = $4;
                        node->withClause = $1;
                }
+       | with_clause select_expr_body order_clause result_offset_clause 
fetch_first_clause
+               {
+                       SelectExprNode* node = $$ = newNode<SelectExprNode>();
+                       node->querySpec = $2;
+                       node->orderClause = $3;
+                       if ($4 || $5) {
+                               RowsClause* rowsNode = newNode<RowsClause>();
+                               rowsNode->skip = $4;
+                               rowsNode->length = $5;
+                               node->rowsClause = rowsNode;
+                       } else {
+                               node->rowsClause = NULL;
+                       }
+                       node->withClause = $1;
+               }
        ;
 
 %type <withClause> with_clause
@@ -5446,14 +5463,13 @@
        | LAST  { $$ = OrderNode::NULLS_LAST; }
        ;
 
-// ROWS clause
+// ROWS clause - ROWS clause should be considered deprecated in favor of 
OFFSET .. FETCH ..
 
+// Non-optional - for use in select_expr (so it doesn't cause conflicts with 
OFFSET .. FETCH ..)
 %type <rowsClause> rows_clause
 rows_clause
-       : // nothing
-               { $$ = NULL; }
        // equivalent to FIRST value
-       | ROWS value
+       : ROWS value
                {
                        $$ = newNode<RowsClause>();
                        $$->length = $2;
@@ -5468,7 +5484,46 @@
                }
        ;
 
+// Optional - for use in delete_searched and update_searched
+%type <rowsClause> rows_clause_optional
+rows_clause_optional
+       : // nothing
+               { $$ = NULL; }
+       | rows_clause
+       ;
 
+// OFFSET n {ROW | ROWS}
+
+row_noise
+       : ROW
+       | ROWS
+       ;
+
+%type <valueExprNode> result_offset_clause
+result_offset_clause
+       : // nothing
+               { $$ = NULL; }
+       | OFFSET simple_value_spec row_noise
+               { $$ = $2; }
+       ;
+
+// FETCH {FIRST | NEXT} n {ROW | ROWS} ONLY
+
+first_next_noise
+       : FIRST
+       | NEXT
+       ;
+
+%type <valueExprNode> fetch_first_clause
+fetch_first_clause
+       : // nothing
+               { $$ = NULL; }
+       | FETCH first_next_noise simple_value_spec row_noise ONLY
+               { $$ = $3; }
+       | FETCH first_next_noise row_noise ONLY
+               { $$ = MAKE_const_slong(1); }
+       ;
+
 // INSERT statement
 // IBO hack: replace column_parens_opt by ins_column_parens_opt.
 %type <storeNode> insert
@@ -5583,7 +5638,7 @@
 
 %type <stmtNode> delete_searched
 delete_searched
-       : KW_DELETE FROM table_name where_clause plan_clause order_clause 
rows_clause returning_clause
+       : KW_DELETE FROM table_name where_clause plan_clause order_clause 
rows_clause_optional returning_clause
                {
                        EraseNode* node = newNode<EraseNode>();
                        node->dsqlRelation = $3;
@@ -5620,7 +5675,7 @@
 %type <stmtNode> update_searched
 update_searched
        : UPDATE table_name SET assignments where_clause plan_clause
-                       order_clause rows_clause returning_clause
+                       order_clause rows_clause_optional returning_clause
                {
                        ModifyNode* node = newNode<ModifyNode>();
                        node->dsqlRelation = $2;
@@ -6339,6 +6394,14 @@
        | '(' value_primary ')' { $$ = $2; }
        ;
 
+// Matches definition of <simple value specification> in SQL standard
+%type <valueExprNode> simple_value_spec
+simple_value_spec
+       : constant
+       | variable
+       | parameter
+       ;
+
 %type <valueExprNode> nonparenthesized_value
 nonparenthesized_value
        : column_name
Index: src/include/gen/msgs.h
===================================================================
--- src/include/gen/msgs.h      (revision 60178)
+++ src/include/gen/msgs.h      (working copy)
@@ -518,8 +518,8 @@
        {335544814, "Services functionality will be supported in a later 
version  of the product"},             /* service_not_supported */
        {335544815, "GENERATOR @1"},            /* generator_name */
        {335544816, "UDF @1"},          /* udf_name */
-       {335544817, "Invalid parameter to FIRST.  Only integers >= 0 are 
allowed."},            /* bad_limit_param */
-       {335544818, "Invalid parameter to SKIP.  Only integers >= 0 are 
allowed."},             /* bad_skip_param */
+       {335544817, "Invalid parameter to FETCH or FIRST. Only integers >= 0 
are allowed."},            /* bad_limit_param */
+       {335544818, "Invalid parameter to OFFSET or SKIP. Only integers >= 0 
are allowed."},            /* bad_skip_param */
        {335544819, "File exceeded maximum size of 2GB.  Add another database 
file or use a 64 bit I/O version of Firebird."},          /* 
io_32bit_exceeded_err */
        {335544820, "Unable to find savepoint with name @1 in transaction 
context"},            /* invalid_savepoint */
        {335544821, "Invalid column position used in the @1 clause"},           
/* dsql_column_pos_err */
@@ -1157,7 +1157,7 @@
        {336397324, "CREATE GENERATOR @1 failed"},              /* 
dsql_create_generator_failed */
        {336397325, "SET GENERATOR @1 failed"},         /* 
dsql_set_generator_failed */
        {336397326, "WITH LOCK can be used only with a single physical table"}, 
        /* dsql_wlock_simple */
-       {336397327, "FIRST/SKIP cannot be used with ROWS"},             /* 
dsql_firstskip_rows */
+       {336397327, "FIRST/SKIP cannot be used with OFFSET/FETCH or ROWS"},     
        /* dsql_firstskip_rows */
        {336397328, "WITH LOCK cannot be used with aggregates"},                
/* dsql_wlock_aggregates */
        {336397329, "WITH LOCK cannot be used with @1"},                /* 
dsql_wlock_conflict */
        {336723983, "unable to open database"},         /* gsec_cant_open_db */
Index: src/msgs/messages2.sql
===================================================================
--- src/msgs/messages2.sql      (revision 60178)
+++ src/msgs/messages2.sql      (working copy)
@@ -593,8 +593,8 @@
 ('service_not_supported', 'SVC_attach', 'svc.c', NULL, 0, 494, NULL, 'Services 
functionality will be supported in a later version  of the product', NULL, 
NULL);
 ('generator_name', 'check_dependencies', 'dfw.e', NULL, 0, 495, NULL, 
'GENERATOR @1', NULL, NULL);
 ('udf_name', 'check_dependencies', 'dfw.e', NULL, 0, 496, NULL, 'UDF @1', 
NULL, NULL);
-('bad_limit_param', 'RSE_open', 'rse.c', NULL, 0, 497, NULL, 'Invalid 
parameter to FIRST.  Only integers >= 0 are allowed.', NULL, NULL);
-('bad_skip_param', 'RSE_open', 'rse.c', NULL, 0, 498, NULL, 'Invalid parameter 
to SKIP.  Only integers >= 0 are allowed.', NULL, NULL);
+('bad_limit_param', 'RSE_open', 'rse.c', NULL, 0, 497, NULL, 'Invalid 
parameter to FETCH or FIRST. Only integers >= 0 are allowed.', NULL, NULL);
+('bad_skip_param', 'RSE_open', 'rse.c', NULL, 0, 498, NULL, 'Invalid parameter 
to OFFSET or SKIP. Only integers >= 0 are allowed.', NULL, NULL);
 ('io_32bit_exceeded_err', 'seek_file', 'unix.c', NULL, 0, 499, NULL, 'File 
exceeded maximum size of 2GB.  Add another database file or use a 64 bit I/O 
version of Firebird.', NULL, NULL);
 ('invalid_savepoint', 'looper', 'exe.cpp', NULL, 0, 500, NULL, 'Unable to find 
savepoint with name @1 in transaction context', NULL, NULL);
 ('dsql_column_pos_err', '(several)', 'pass1.cpp', NULL, 0, 501, NULL, 'Invalid 
column position used in the @1 clause', NULL, NULL);
@@ -2606,7 +2606,7 @@
 ('dsql_create_generator_failed', 'putErrorPrefix', 'DdlNodes.h', NULL, 13, 
1036, NULL, 'CREATE GENERATOR @1 failed', NULL, NULL);
 ('dsql_set_generator_failed', 'putErrorPrefix', 'DdlNodes.h', NULL, 13, 1037, 
NULL, 'SET GENERATOR @1 failed', NULL, NULL);
 ('dsql_wlock_simple', 'pass1_rse_impl', 'pass1.cpp', NULL, 13, 1038, NULL, 
'WITH LOCK can be used only with a single physical table', NULL, NULL);
-('dsql_firstskip_rows', 'pass1_rse_impl', 'pass1.cpp', NULL, 13, 1039, NULL, 
'FIRST/SKIP cannot be used with ROWS', NULL, NULL);
+('dsql_firstskip_rows', 'pass1_rse_impl', 'pass1.cpp', NULL, 13, 1039, NULL, 
'FIRST/SKIP cannot be used with OFFSET/FETCH or ROWS', NULL, NULL);
 ('dsql_wlock_aggregates', 'pass1_rse_impl', 'pass1.cpp', NULL, 13, 1040, NULL, 
'WITH LOCK cannot be used with aggregates', NULL, NULL);
 ('dsql_wlock_conflict', NULL, 'pass1.cpp', NULL, 13, 1041, NULL, 'WITH LOCK 
cannot be used with @1', NULL, NULL);
 -- SQLWARN
Index: src/yvalve/keywords.cpp
===================================================================
--- src/yvalve/keywords.cpp     (revision 60178)
+++ src/yvalve/keywords.cpp     (working copy)
@@ -282,6 +282,7 @@
        {NULLS, "NULLS", 2, true},
        {KW_NUMERIC, "NUMERIC", 1, false},
        {OCTET_LENGTH, "OCTET_LENGTH", 2, false},
+       {OFFSET, "OFFSET", 2, false},
        {OF, "OF", 1, false},
        {ON, "ON", 1, false},
        {ONLY, "ONLY", 1, false},
@@ -348,6 +349,7 @@
        {ROLE, "ROLE", 1, true},
        {ROLLBACK, "ROLLBACK", 1, false},
        {ROUND, "ROUND", 2, false},
+       {ROW, "ROW", 2, false},
        {ROW_COUNT, "ROW_COUNT", 2, false},
        {ROW_NUMBER, "ROW_NUMBER", 2, false},
        {ROWS, "ROWS", 2, false},
------------------------------------------------------------------------------
Firebird-Devel mailing list, web interface at 
https://lists.sourceforge.net/lists/listinfo/firebird-devel

Reply via email to