public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH 7/8] Add tests for compression methods
108+ messages / 5 participants
[nested] [flat]

* [PATCH 7/8] Add tests for compression methods
@ 2018-06-18 12:59 Ildus Kurbangaliev <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Ildus Kurbangaliev @ 2018-06-18 12:59 UTC (permalink / raw)

Signed-off-by: Ildus Kurbangaliev <[email protected]>
---
 contrib/test_decoding/expected/ddl.out        |  50 +--
 src/backend/access/compression/cm_zlib.c      |   2 +-
 src/test/regress/expected/copy2.out           |   8 +-
 src/test/regress/expected/create_cm.out       | 405 +++++++++++++++++
 src/test/regress/expected/create_cm_1.out     | 409 ++++++++++++++++++
 src/test/regress/expected/create_table.out    | 132 +++---
 .../regress/expected/create_table_like.out    |  68 +--
 src/test/regress/expected/domain.out          |  16 +-
 src/test/regress/expected/foreign_data.out    | 258 +++++------
 src/test/regress/expected/inherit.out         | 138 +++---
 src/test/regress/expected/insert.out          | 118 ++---
 src/test/regress/expected/opr_sanity.out      |   4 +-
 src/test/regress/expected/publication.out     |  40 +-
 .../regress/expected/replica_identity.out     |  14 +-
 src/test/regress/expected/rowsecurity.out     |  16 +-
 src/test/regress/expected/rules.out           |  20 +-
 src/test/regress/expected/sanity_check.out    |   3 +
 src/test/regress/expected/update.out          |  16 +-
 src/test/regress/parallel_schedule            |   1 +
 src/test/regress/serial_schedule              |   1 +
 src/test/regress/sql/create_cm.sql            | 203 +++++++++
 src/test/regress/sql/opr_sanity.sql           |   4 +-
 22 files changed, 1476 insertions(+), 450 deletions(-)
 create mode 100644 src/test/regress/expected/create_cm.out
 create mode 100644 src/test/regress/expected/create_cm_1.out
 create mode 100644 src/test/regress/sql/create_cm.sql

diff --git a/contrib/test_decoding/expected/ddl.out b/contrib/test_decoding/expected/ddl.out
index 2bd28e6d15..01947e0696 100644
--- a/contrib/test_decoding/expected/ddl.out
+++ b/contrib/test_decoding/expected/ddl.out
@@ -438,12 +438,12 @@ CREATE TABLE replication_metadata (
 WITH (user_catalog_table = true)
 ;
 \d+ replication_metadata
-                                                 Table "public.replication_metadata"
-  Column  |  Type   | Collation | Nullable |                     Default                      | Storage  | Stats target | Description 
-----------+---------+-----------+----------+--------------------------------------------------+----------+--------------+-------------
- id       | integer |           | not null | nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
- relation | name    |           | not null |                                                  | plain    |              | 
- options  | text[]  |           |          |                                                  | extended |              | 
+                                                        Table "public.replication_metadata"
+  Column  |  Type   | Collation | Nullable |                     Default                      | Storage  | Compression | Stats target | Description 
+----------+---------+-----------+----------+--------------------------------------------------+----------+-------------+--------------+-------------
+ id       | integer |           | not null | nextval('replication_metadata_id_seq'::regclass) | plain    |             |              | 
+ relation | name    |           | not null |                                                  | plain    |             |              | 
+ options  | text[]  |           |          |                                                  | extended | pglz        |              | 
 Indexes:
     "replication_metadata_pkey" PRIMARY KEY, btree (id)
 Options: user_catalog_table=true
@@ -452,12 +452,12 @@ INSERT INTO replication_metadata(relation, options)
 VALUES ('foo', ARRAY['a', 'b']);
 ALTER TABLE replication_metadata RESET (user_catalog_table);
 \d+ replication_metadata
-                                                 Table "public.replication_metadata"
-  Column  |  Type   | Collation | Nullable |                     Default                      | Storage  | Stats target | Description 
-----------+---------+-----------+----------+--------------------------------------------------+----------+--------------+-------------
- id       | integer |           | not null | nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
- relation | name    |           | not null |                                                  | plain    |              | 
- options  | text[]  |           |          |                                                  | extended |              | 
+                                                        Table "public.replication_metadata"
+  Column  |  Type   | Collation | Nullable |                     Default                      | Storage  | Compression | Stats target | Description 
+----------+---------+-----------+----------+--------------------------------------------------+----------+-------------+--------------+-------------
+ id       | integer |           | not null | nextval('replication_metadata_id_seq'::regclass) | plain    |             |              | 
+ relation | name    |           | not null |                                                  | plain    |             |              | 
+ options  | text[]  |           |          |                                                  | extended | pglz        |              | 
 Indexes:
     "replication_metadata_pkey" PRIMARY KEY, btree (id)
 
@@ -465,12 +465,12 @@ INSERT INTO replication_metadata(relation, options)
 VALUES ('bar', ARRAY['a', 'b']);
 ALTER TABLE replication_metadata SET (user_catalog_table = true);
 \d+ replication_metadata
-                                                 Table "public.replication_metadata"
-  Column  |  Type   | Collation | Nullable |                     Default                      | Storage  | Stats target | Description 
-----------+---------+-----------+----------+--------------------------------------------------+----------+--------------+-------------
- id       | integer |           | not null | nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
- relation | name    |           | not null |                                                  | plain    |              | 
- options  | text[]  |           |          |                                                  | extended |              | 
+                                                        Table "public.replication_metadata"
+  Column  |  Type   | Collation | Nullable |                     Default                      | Storage  | Compression | Stats target | Description 
+----------+---------+-----------+----------+--------------------------------------------------+----------+-------------+--------------+-------------
+ id       | integer |           | not null | nextval('replication_metadata_id_seq'::regclass) | plain    |             |              | 
+ relation | name    |           | not null |                                                  | plain    |             |              | 
+ options  | text[]  |           |          |                                                  | extended | pglz        |              | 
 Indexes:
     "replication_metadata_pkey" PRIMARY KEY, btree (id)
 Options: user_catalog_table=true
@@ -483,13 +483,13 @@ ALTER TABLE replication_metadata ALTER COLUMN rewritemeornot TYPE text;
 ERROR:  cannot rewrite table "replication_metadata" used as a catalog table
 ALTER TABLE replication_metadata SET (user_catalog_table = false);
 \d+ replication_metadata
-                                                    Table "public.replication_metadata"
-     Column     |  Type   | Collation | Nullable |                     Default                      | Storage  | Stats target | Description 
-----------------+---------+-----------+----------+--------------------------------------------------+----------+--------------+-------------
- id             | integer |           | not null | nextval('replication_metadata_id_seq'::regclass) | plain    |              | 
- relation       | name    |           | not null |                                                  | plain    |              | 
- options        | text[]  |           |          |                                                  | extended |              | 
- rewritemeornot | integer |           |          |                                                  | plain    |              | 
+                                                           Table "public.replication_metadata"
+     Column     |  Type   | Collation | Nullable |                     Default                      | Storage  | Compression | Stats target | Description 
+----------------+---------+-----------+----------+--------------------------------------------------+----------+-------------+--------------+-------------
+ id             | integer |           | not null | nextval('replication_metadata_id_seq'::regclass) | plain    |             |              | 
+ relation       | name    |           | not null |                                                  | plain    |             |              | 
+ options        | text[]  |           |          |                                                  | extended | pglz        |              | 
+ rewritemeornot | integer |           |          |                                                  | plain    |             |              | 
 Indexes:
     "replication_metadata_pkey" PRIMARY KEY, btree (id)
 Options: user_catalog_table=false
diff --git a/src/backend/access/compression/cm_zlib.c b/src/backend/access/compression/cm_zlib.c
index 0dcb56ddf3..dc8666f309 100644
--- a/src/backend/access/compression/cm_zlib.c
+++ b/src/backend/access/compression/cm_zlib.c
@@ -182,7 +182,6 @@ zlib_cmcompress(CompressionAmOptions *cmoptions, const struct varlena *value)
 	}
 
 	pfree(tmp);
-#endif
 	return NULL;
 }
 
@@ -231,6 +230,7 @@ zlib_cmdecompress(CompressionAmOptions *cmoptions, const struct varlena *value)
 	pfree(zp);
 	return result;
 }
+#endif
 
 Datum
 zlibhandler(PG_FUNCTION_ARGS)
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index 75d4119eaa..54fe30b3c0 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -464,10 +464,10 @@ begin
 end $$ language plpgsql immutable;
 alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
 \d+ check_con_tbl
-                               Table "public.check_con_tbl"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- f1     | integer |           |          |         | plain   |              | 
+                                      Table "public.check_con_tbl"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1     | integer |           |          |         | plain   |             |              | 
 Check constraints:
     "check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
 
diff --git a/src/test/regress/expected/create_cm.out b/src/test/regress/expected/create_cm.out
new file mode 100644
index 0000000000..de14d87885
--- /dev/null
+++ b/src/test/regress/expected/create_cm.out
@@ -0,0 +1,405 @@
+-- test drop
+DROP ACCESS METHOD pglz; --fail
+ERROR:  cannot drop access method pglz because it is required by the database system
+CREATE ACCESS METHOD pglz1 TYPE COMPRESSION HANDLER pglzhandler;
+CREATE TABLE droptest(d1 TEXT COMPRESSION pglz1);
+DROP ACCESS METHOD pglz1;
+ERROR:  cannot drop access method pglz1 because other objects depend on it
+DETAIL:  column d1 of table droptest depends on access method pglz1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP ACCESS METHOD pglz1 CASCADE;
+NOTICE:  drop cascades to column d1 of table droptest
+\d+ droptest
+                                       Table "public.droptest"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+---------+-------------+--------------+-------------
+
+DROP TABLE droptest;
+CREATE ACCESS METHOD pglz1 TYPE COMPRESSION HANDLER pglzhandler;
+-- test auto drop of related attribute compression
+CREATE ACCESS METHOD pglz2 TYPE COMPRESSION HANDLER pglzhandler;
+CREATE TABLE cmaltertest (f1 TEXT COMPRESSION pglz2);
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass;
+ acname | acattnum | acoptions 
+--------+----------+-----------
+ pglz2  |        1 | 
+(1 row)
+
+ALTER TABLE cmaltertest DROP COLUMN f1;
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass;
+ acname | acattnum | acoptions 
+--------+----------+-----------
+(0 rows)
+
+DROP TABLE cmaltertest;
+-- test drop
+DROP ACCESS METHOD pglz2;
+-- test alter data type
+CREATE TABLE cmaltertest(at1 TEXT);
+ALTER TABLE cmaltertest ALTER COLUMN at1 SET COMPRESSION pglz1 PRESERVE (pglz);
+SELECT pg_column_compression('cmaltertest', 'at1');
+ pg_column_compression 
+-----------------------
+ pglz, pglz1
+(1 row)
+
+ALTER TABLE cmaltertest ALTER COLUMN at1 SET DATA TYPE INT USING at1::INTEGER;
+\d+ cmaltertest
+                                       Table "public.cmaltertest"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ at1    | integer |           |          |         | plain   |             |              | 
+
+SELECT pg_column_compression('cmaltertest', 'at1');
+ pg_column_compression 
+-----------------------
+ 
+(1 row)
+
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass;
+ acname | acattnum | acoptions 
+--------+----------+-----------
+(0 rows)
+
+ALTER TABLE cmaltertest ALTER COLUMN at1 SET DATA TYPE TEXT;
+SELECT pg_column_compression('cmaltertest', 'at1');
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+DROP TABLE cmaltertest;
+-- test storages
+CREATE TABLE cmstoragetest(st1 TEXT, st2 INT);
+ALTER TABLE cmstoragetest ALTER COLUMN st2
+	SET COMPRESSION pglz WITH (min_input_size '100'); -- fail
+ERROR:  column data type integer does not support compression
+ALTER TABLE cmstoragetest ALTER COLUMN st1
+	SET COMPRESSION pglz WITH (min_input_size '100', min_comp_rate '50');
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE EXTERNAL;
+\d+ cmstoragetest
+                                                        Table "public.cmstoragetest"
+ Column |  Type   | Collation | Nullable | Default | Storage  |                  Compression                   | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------------------------------------------+--------------+-------------
+ st1    | text    |           |          |         | external | pglz(min_comp_rate '50', min_input_size '100') |              | 
+ st2    | integer |           |          |         | plain    |                                                |              | 
+
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE MAIN;
+\d+ cmstoragetest
+                                                       Table "public.cmstoragetest"
+ Column |  Type   | Collation | Nullable | Default | Storage |                  Compression                   | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+------------------------------------------------+--------------+-------------
+ st1    | text    |           |          |         | main    | pglz(min_comp_rate '50', min_input_size '100') |              | 
+ st2    | integer |           |          |         | plain   |                                                |              | 
+
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE PLAIN;
+\d+ cmstoragetest
+                                                       Table "public.cmstoragetest"
+ Column |  Type   | Collation | Nullable | Default | Storage |                  Compression                   | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+------------------------------------------------+--------------+-------------
+ st1    | text    |           |          |         | plain   | pglz(min_comp_rate '50', min_input_size '100') |              | 
+ st2    | integer |           |          |         | plain   |                                                |              | 
+
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE EXTENDED;
+\d+ cmstoragetest
+                                                        Table "public.cmstoragetest"
+ Column |  Type   | Collation | Nullable | Default | Storage  |                  Compression                   | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------------------------------------------+--------------+-------------
+ st1    | text    |           |          |         | extended | pglz(min_comp_rate '50', min_input_size '100') |              | 
+ st2    | integer |           |          |         | plain    |                                                |              | 
+
+DROP TABLE cmstoragetest;
+CREATE ACCESS METHOD pglz2 TYPE COMPRESSION HANDLER pglzhandler;
+-- test PRESERVE
+CREATE TABLE cmtest(f1 TEXT);
+\d+ cmtest
+                                        Table "public.cmtest"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1     | text |           |          |         | extended | pglz        |              | 
+
+-- view to check dependencies
+CREATE VIEW cmtest_deps AS
+	SELECT classid, objsubid, refclassid, refobjsubid, deptype
+	FROM pg_depend
+	WHERE (refclassid = 4001 OR classid = 4001) AND
+		  (objid = 'cmtest'::regclass OR refobjid = 'cmtest'::regclass)
+	ORDER by objid, refobjid;
+INSERT INTO cmtest VALUES(repeat('1234567890',1001));
+-- one normal dependency
+SELECT * FROM cmtest_deps;
+ classid | objsubid | refclassid | refobjsubid | deptype 
+---------+----------+------------+-------------+---------
+    1259 |        1 |       4001 |           0 | n
+(1 row)
+
+-- check decompression
+SELECT length(f1) FROM cmtest;
+ length 
+--------
+  10010
+(1 row)
+
+SELECT length(f1) FROM cmtest;
+ length 
+--------
+  10010
+(1 row)
+
+CREATE FUNCTION on_cmtest_rewrite()
+RETURNS event_trigger AS $$
+BEGIN
+	RAISE NOTICE 'cmtest rewrite';
+END;
+$$  LANGUAGE plpgsql;
+CREATE EVENT TRIGGER notice_on_cmtest_rewrite ON table_rewrite
+	EXECUTE PROCEDURE on_cmtest_rewrite();
+-- no rewrite
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz1 PRESERVE (pglz);
+INSERT INTO cmtest VALUES(repeat('1234567890',1002));
+SELECT length(f1) FROM cmtest;
+ length 
+--------
+  10010
+  10020
+(2 rows)
+
+SELECT pg_column_compression('cmtest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz, pglz1
+(1 row)
+
+-- one normal and one internal dependency
+SELECT * FROM cmtest_deps;
+ classid | objsubid | refclassid | refobjsubid | deptype 
+---------+----------+------------+-------------+---------
+    1259 |        1 |       4001 |           0 | n
+    4001 |        0 |       1259 |           1 | i
+(2 rows)
+
+-- rewrite
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz2 PRESERVE (pglz1);
+NOTICE:  cmtest rewrite
+INSERT INTO cmtest VALUES(repeat('1234567890',1003));
+SELECT length(f1) FROM cmtest;
+ length 
+--------
+  10010
+  10020
+  10030
+(3 rows)
+
+SELECT pg_column_compression('cmtest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz2, pglz1
+(1 row)
+
+-- two internal dependencies
+SELECT * FROM cmtest_deps;
+ classid | objsubid | refclassid | refobjsubid | deptype 
+---------+----------+------------+-------------+---------
+    4001 |        0 |       1259 |           1 | i
+    4001 |        0 |       1259 |           1 | i
+(2 rows)
+
+-- rewrite
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz;
+NOTICE:  cmtest rewrite
+INSERT INTO cmtest VALUES(repeat('1234567890',1004));
+SELECT length(f1) FROM cmtest;
+ length 
+--------
+  10010
+  10020
+  10030
+  10040
+(4 rows)
+
+SELECT pg_column_compression('cmtest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+-- one nornal dependency
+SELECT * FROM cmtest_deps;
+ classid | objsubid | refclassid | refobjsubid | deptype 
+---------+----------+------------+-------------+---------
+    1259 |        1 |       4001 |           0 | n
+(1 row)
+
+-- no rewrites
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz1 PRESERVE (pglz);
+INSERT INTO cmtest VALUES(repeat('1234567890',1005));
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz
+	WITH (min_input_size '1000') PRESERVE (pglz, pglz1);
+INSERT INTO cmtest VALUES(repeat('1234567890',1006));
+-- one nornal dependency and two internal dependencies
+SELECT * FROM cmtest_deps;
+ classid | objsubid | refclassid | refobjsubid | deptype 
+---------+----------+------------+-------------+---------
+    1259 |        1 |       4001 |           0 | n
+    4001 |        0 |       1259 |           1 | i
+    4001 |        0 |       1259 |           1 | i
+(3 rows)
+
+-- remove function and related event trigger
+DROP FUNCTION on_cmtest_rewrite CASCADE;
+NOTICE:  drop cascades to event trigger notice_on_cmtest_rewrite
+-- test moving
+CREATE TABLE cmdata(f1 text COMPRESSION pglz WITH (first_success_by '10000'));
+INSERT INTO cmdata VALUES(repeat('1234567890',1000));
+INSERT INTO cmdata VALUES(repeat('1234567890',1001));
+-- copy with table creation
+SELECT * INTO cmmove1 FROM cmdata;
+-- we update using datum from different table
+CREATE TABLE cmmove2(f1 text COMPRESSION pglz WITH (min_input_size '100'));
+INSERT INTO cmmove2 VALUES (repeat('1234567890',1004));
+UPDATE cmmove2 SET f1 = cmdata.f1 FROM cmdata;
+-- copy to existing table
+CREATE TABLE cmmove3(f1 text COMPRESSION pglz WITH (min_input_size '100'));
+INSERT INTO cmmove3 SELECT * FROM cmdata;
+-- drop original compression information
+DROP TABLE cmdata;
+-- check data is ok
+SELECT length(f1) FROM cmmove1;
+ length 
+--------
+  10000
+  10010
+(2 rows)
+
+SELECT length(f1) FROM cmmove2;
+ length 
+--------
+  10000
+(1 row)
+
+SELECT length(f1) FROM cmmove3;
+ length 
+--------
+  10000
+  10010
+(2 rows)
+
+-- create different types of tables
+CREATE TABLE cmexample (f1 TEXT COMPRESSION pglz1);
+CREATE TABLE cmtestlike1 (LIKE cmexample INCLUDING COMPRESSION);
+CREATE TABLE cmtestlike2 (f2 INT) INHERITS (cmexample);
+\d+ cmtestlike1
+                                      Table "public.cmtestlike1"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1     | text |           |          |         | extended | pglz1       |              | 
+
+\d+ cmtestlike2
+                                       Table "public.cmtestlike2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1     | text    |           |          |         | extended | pglz1       |              | 
+ f2     | integer |           |          |         | plain    |             |              | 
+Inherits: cmexample
+
+-- test two columns
+CREATE TABLE cmaltertest(f1 TEXT, f2 TEXT COMPRESSION pglz1);
+\d+ cmaltertest;
+                                      Table "public.cmaltertest"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1     | text |           |          |         | extended | pglz        |              | 
+ f2     | text |           |          |         | extended | pglz1       |              | 
+
+-- fail, changing one column twice
+ALTER TABLE cmaltertest ALTER COLUMN f1 SET COMPRESSION pglz,
+	ALTER COLUMN f1 SET COMPRESSION pglz;
+ERROR:  cannot alter compression of column "f1" twice
+HINT:  Remove one of statements from the command.
+-- with rewrite
+ALTER TABLE cmaltertest ALTER COLUMN f1 SET COMPRESSION pglz1,
+	ALTER COLUMN f2 SET COMPRESSION pglz PRESERVE (pglz1);
+SELECT pg_column_compression('cmaltertest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz1
+(1 row)
+
+SELECT pg_column_compression('cmaltertest', 'f2');
+ pg_column_compression 
+-----------------------
+ pglz, pglz1
+(1 row)
+
+-- no rewrite
+ALTER TABLE cmaltertest ALTER COLUMN f1 SET COMPRESSION pglz PRESERVE (pglz1),
+	ALTER COLUMN f2 SET COMPRESSION pglz2 PRESERVE (pglz1, pglz);
+SELECT pg_column_compression('cmaltertest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz, pglz1
+(1 row)
+
+SELECT pg_column_compression('cmaltertest', 'f2');
+ pg_column_compression 
+-----------------------
+ pglz, pglz2, pglz1
+(1 row)
+
+-- make pglz2 droppable
+ALTER TABLE cmaltertest ALTER COLUMN f2 SET COMPRESSION pglz1;
+SELECT pg_column_compression('cmaltertest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz, pglz1
+(1 row)
+
+SELECT pg_column_compression('cmaltertest', 'f2');
+ pg_column_compression 
+-----------------------
+ pglz1
+(1 row)
+
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass OR acrelid = 'cmtest'::regclass;
+ acname | acattnum |       acoptions       
+--------+----------+-----------------------
+ pglz1  |        1 | 
+ pglz   |        1 | {min_input_size=1000}
+ pglz1  |        2 | 
+ pglz1  |        1 | 
+(4 rows)
+
+-- zlib compression
+CREATE TABLE zlibtest(f1 TEXT COMPRESSION zlib WITH (invalid 'param'));
+ERROR:  unexpected parameter for zlib: "invalid"
+CREATE TABLE zlibtest(f1 TEXT COMPRESSION zlib WITH (level 'best'));
+ERROR:  unexpected value for zlib compression level: "best"
+CREATE TABLE zlibtest(f1 TEXT COMPRESSION zlib);
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (level 'best_compression');
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (level 'best_speed');
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (level 'default');
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (dict 'one');
+ERROR:  zlib dictionary is too small
+INSERT INTO zlibtest VALUES(repeat('1234567890',1004));
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (dict 'one two') PRESERVE (zlib);
+INSERT INTO zlibtest VALUES(repeat('1234567890 one two three',1004));
+SELECT length(f1) FROM zlibtest;
+ length 
+--------
+  10040
+  24096
+(2 rows)
+
+DROP ACCESS METHOD pglz2;
+DROP VIEW cmtest_deps;
+DROP TABLE cmmove1, cmmove2, cmmove3;
+DROP TABLE cmexample, cmtestlike1, cmtestlike2, zlibtest;
diff --git a/src/test/regress/expected/create_cm_1.out b/src/test/regress/expected/create_cm_1.out
new file mode 100644
index 0000000000..755d40caef
--- /dev/null
+++ b/src/test/regress/expected/create_cm_1.out
@@ -0,0 +1,409 @@
+-- test drop
+DROP ACCESS METHOD pglz; --fail
+ERROR:  cannot drop access method pglz because it is required by the database system
+CREATE ACCESS METHOD pglz1 TYPE COMPRESSION HANDLER pglzhandler;
+CREATE TABLE droptest(d1 TEXT COMPRESSION pglz1);
+DROP ACCESS METHOD pglz1;
+ERROR:  cannot drop access method pglz1 because other objects depend on it
+DETAIL:  column d1 of table droptest depends on access method pglz1
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP ACCESS METHOD pglz1 CASCADE;
+NOTICE:  drop cascades to column d1 of table droptest
+\d+ droptest
+                                       Table "public.droptest"
+ Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+---------+-------------+--------------+-------------
+
+DROP TABLE droptest;
+CREATE ACCESS METHOD pglz1 TYPE COMPRESSION HANDLER pglzhandler;
+-- test auto drop of related attribute compression
+CREATE ACCESS METHOD pglz2 TYPE COMPRESSION HANDLER pglzhandler;
+CREATE TABLE cmaltertest (f1 TEXT COMPRESSION pglz2);
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass;
+ acname | acattnum | acoptions 
+--------+----------+-----------
+ pglz2  |        1 | 
+(1 row)
+
+ALTER TABLE cmaltertest DROP COLUMN f1;
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass;
+ acname | acattnum | acoptions 
+--------+----------+-----------
+(0 rows)
+
+DROP TABLE cmaltertest;
+-- test drop
+DROP ACCESS METHOD pglz2;
+-- test alter data type
+CREATE TABLE cmaltertest(at1 TEXT);
+ALTER TABLE cmaltertest ALTER COLUMN at1 SET COMPRESSION pglz1 PRESERVE (pglz);
+SELECT pg_column_compression('cmaltertest', 'at1');
+ pg_column_compression 
+-----------------------
+ pglz, pglz1
+(1 row)
+
+ALTER TABLE cmaltertest ALTER COLUMN at1 SET DATA TYPE INT USING at1::INTEGER;
+\d+ cmaltertest
+                                       Table "public.cmaltertest"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ at1    | integer |           |          |         | plain   |             |              | 
+
+SELECT pg_column_compression('cmaltertest', 'at1');
+ pg_column_compression 
+-----------------------
+ 
+(1 row)
+
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass;
+ acname | acattnum | acoptions 
+--------+----------+-----------
+(0 rows)
+
+ALTER TABLE cmaltertest ALTER COLUMN at1 SET DATA TYPE TEXT;
+SELECT pg_column_compression('cmaltertest', 'at1');
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+DROP TABLE cmaltertest;
+-- test storages
+CREATE TABLE cmstoragetest(st1 TEXT, st2 INT);
+ALTER TABLE cmstoragetest ALTER COLUMN st2
+	SET COMPRESSION pglz WITH (min_input_size '100'); -- fail
+ERROR:  column data type integer does not support compression
+ALTER TABLE cmstoragetest ALTER COLUMN st1
+	SET COMPRESSION pglz WITH (min_input_size '100', min_comp_rate '50');
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE EXTERNAL;
+\d+ cmstoragetest
+                                                        Table "public.cmstoragetest"
+ Column |  Type   | Collation | Nullable | Default | Storage  |                  Compression                   | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------------------------------------------+--------------+-------------
+ st1    | text    |           |          |         | external | pglz(min_comp_rate '50', min_input_size '100') |              | 
+ st2    | integer |           |          |         | plain    |                                                |              | 
+
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE MAIN;
+\d+ cmstoragetest
+                                                       Table "public.cmstoragetest"
+ Column |  Type   | Collation | Nullable | Default | Storage |                  Compression                   | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+------------------------------------------------+--------------+-------------
+ st1    | text    |           |          |         | main    | pglz(min_comp_rate '50', min_input_size '100') |              | 
+ st2    | integer |           |          |         | plain   |                                                |              | 
+
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE PLAIN;
+\d+ cmstoragetest
+                                                       Table "public.cmstoragetest"
+ Column |  Type   | Collation | Nullable | Default | Storage |                  Compression                   | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+------------------------------------------------+--------------+-------------
+ st1    | text    |           |          |         | plain   | pglz(min_comp_rate '50', min_input_size '100') |              | 
+ st2    | integer |           |          |         | plain   |                                                |              | 
+
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE EXTENDED;
+\d+ cmstoragetest
+                                                        Table "public.cmstoragetest"
+ Column |  Type   | Collation | Nullable | Default | Storage  |                  Compression                   | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+------------------------------------------------+--------------+-------------
+ st1    | text    |           |          |         | extended | pglz(min_comp_rate '50', min_input_size '100') |              | 
+ st2    | integer |           |          |         | plain    |                                                |              | 
+
+DROP TABLE cmstoragetest;
+CREATE ACCESS METHOD pglz2 TYPE COMPRESSION HANDLER pglzhandler;
+-- test PRESERVE
+CREATE TABLE cmtest(f1 TEXT);
+\d+ cmtest
+                                        Table "public.cmtest"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1     | text |           |          |         | extended | pglz        |              | 
+
+-- view to check dependencies
+CREATE VIEW cmtest_deps AS
+	SELECT classid, objsubid, refclassid, refobjsubid, deptype
+	FROM pg_depend
+	WHERE (refclassid = 4001 OR classid = 4001) AND
+		  (objid = 'cmtest'::regclass OR refobjid = 'cmtest'::regclass)
+	ORDER by objid, refobjid;
+INSERT INTO cmtest VALUES(repeat('1234567890',1001));
+-- one normal dependency
+SELECT * FROM cmtest_deps;
+ classid | objsubid | refclassid | refobjsubid | deptype 
+---------+----------+------------+-------------+---------
+    1259 |        1 |       4001 |           0 | n
+(1 row)
+
+-- check decompression
+SELECT length(f1) FROM cmtest;
+ length 
+--------
+  10010
+(1 row)
+
+SELECT length(f1) FROM cmtest;
+ length 
+--------
+  10010
+(1 row)
+
+CREATE FUNCTION on_cmtest_rewrite()
+RETURNS event_trigger AS $$
+BEGIN
+	RAISE NOTICE 'cmtest rewrite';
+END;
+$$  LANGUAGE plpgsql;
+CREATE EVENT TRIGGER notice_on_cmtest_rewrite ON table_rewrite
+	EXECUTE PROCEDURE on_cmtest_rewrite();
+-- no rewrite
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz1 PRESERVE (pglz);
+INSERT INTO cmtest VALUES(repeat('1234567890',1002));
+SELECT length(f1) FROM cmtest;
+ length 
+--------
+  10010
+  10020
+(2 rows)
+
+SELECT pg_column_compression('cmtest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz, pglz1
+(1 row)
+
+-- one normal and one internal dependency
+SELECT * FROM cmtest_deps;
+ classid | objsubid | refclassid | refobjsubid | deptype 
+---------+----------+------------+-------------+---------
+    1259 |        1 |       4001 |           0 | n
+    4001 |        0 |       1259 |           1 | i
+(2 rows)
+
+-- rewrite
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz2 PRESERVE (pglz1);
+NOTICE:  cmtest rewrite
+INSERT INTO cmtest VALUES(repeat('1234567890',1003));
+SELECT length(f1) FROM cmtest;
+ length 
+--------
+  10010
+  10020
+  10030
+(3 rows)
+
+SELECT pg_column_compression('cmtest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz2, pglz1
+(1 row)
+
+-- two internal dependencies
+SELECT * FROM cmtest_deps;
+ classid | objsubid | refclassid | refobjsubid | deptype 
+---------+----------+------------+-------------+---------
+    4001 |        0 |       1259 |           1 | i
+    4001 |        0 |       1259 |           1 | i
+(2 rows)
+
+-- rewrite
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz;
+NOTICE:  cmtest rewrite
+INSERT INTO cmtest VALUES(repeat('1234567890',1004));
+SELECT length(f1) FROM cmtest;
+ length 
+--------
+  10010
+  10020
+  10030
+  10040
+(4 rows)
+
+SELECT pg_column_compression('cmtest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz
+(1 row)
+
+-- one nornal dependency
+SELECT * FROM cmtest_deps;
+ classid | objsubid | refclassid | refobjsubid | deptype 
+---------+----------+------------+-------------+---------
+    1259 |        1 |       4001 |           0 | n
+(1 row)
+
+-- no rewrites
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz1 PRESERVE (pglz);
+INSERT INTO cmtest VALUES(repeat('1234567890',1005));
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz
+	WITH (min_input_size '1000') PRESERVE (pglz, pglz1);
+INSERT INTO cmtest VALUES(repeat('1234567890',1006));
+-- one nornal dependency and two internal dependencies
+SELECT * FROM cmtest_deps;
+ classid | objsubid | refclassid | refobjsubid | deptype 
+---------+----------+------------+-------------+---------
+    1259 |        1 |       4001 |           0 | n
+    4001 |        0 |       1259 |           1 | i
+    4001 |        0 |       1259 |           1 | i
+(3 rows)
+
+-- remove function and related event trigger
+DROP FUNCTION on_cmtest_rewrite CASCADE;
+NOTICE:  drop cascades to event trigger notice_on_cmtest_rewrite
+-- test moving
+CREATE TABLE cmdata(f1 text COMPRESSION pglz WITH (first_success_by '10000'));
+INSERT INTO cmdata VALUES(repeat('1234567890',1000));
+INSERT INTO cmdata VALUES(repeat('1234567890',1001));
+-- copy with table creation
+SELECT * INTO cmmove1 FROM cmdata;
+-- we update using datum from different table
+CREATE TABLE cmmove2(f1 text COMPRESSION pglz WITH (min_input_size '100'));
+INSERT INTO cmmove2 VALUES (repeat('1234567890',1004));
+UPDATE cmmove2 SET f1 = cmdata.f1 FROM cmdata;
+-- copy to existing table
+CREATE TABLE cmmove3(f1 text COMPRESSION pglz WITH (min_input_size '100'));
+INSERT INTO cmmove3 SELECT * FROM cmdata;
+-- drop original compression information
+DROP TABLE cmdata;
+-- check data is ok
+SELECT length(f1) FROM cmmove1;
+ length 
+--------
+  10000
+  10010
+(2 rows)
+
+SELECT length(f1) FROM cmmove2;
+ length 
+--------
+  10000
+(1 row)
+
+SELECT length(f1) FROM cmmove3;
+ length 
+--------
+  10000
+  10010
+(2 rows)
+
+-- create different types of tables
+CREATE TABLE cmexample (f1 TEXT COMPRESSION pglz1);
+CREATE TABLE cmtestlike1 (LIKE cmexample INCLUDING COMPRESSION);
+CREATE TABLE cmtestlike2 (f2 INT) INHERITS (cmexample);
+\d+ cmtestlike1
+                                      Table "public.cmtestlike1"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1     | text |           |          |         | extended | pglz1       |              | 
+
+\d+ cmtestlike2
+                                       Table "public.cmtestlike2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1     | text    |           |          |         | extended | pglz1       |              | 
+ f2     | integer |           |          |         | plain    |             |              | 
+Inherits: cmexample
+
+-- test two columns
+CREATE TABLE cmaltertest(f1 TEXT, f2 TEXT COMPRESSION pglz1);
+\d+ cmaltertest;
+                                      Table "public.cmaltertest"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1     | text |           |          |         | extended | pglz        |              | 
+ f2     | text |           |          |         | extended | pglz1       |              | 
+
+-- fail, changing one column twice
+ALTER TABLE cmaltertest ALTER COLUMN f1 SET COMPRESSION pglz,
+	ALTER COLUMN f1 SET COMPRESSION pglz;
+ERROR:  cannot alter compression of column "f1" twice
+HINT:  Remove one of statements from the command.
+-- with rewrite
+ALTER TABLE cmaltertest ALTER COLUMN f1 SET COMPRESSION pglz1,
+	ALTER COLUMN f2 SET COMPRESSION pglz PRESERVE (pglz1);
+SELECT pg_column_compression('cmaltertest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz1
+(1 row)
+
+SELECT pg_column_compression('cmaltertest', 'f2');
+ pg_column_compression 
+-----------------------
+ pglz, pglz1
+(1 row)
+
+-- no rewrite
+ALTER TABLE cmaltertest ALTER COLUMN f1 SET COMPRESSION pglz PRESERVE (pglz1),
+	ALTER COLUMN f2 SET COMPRESSION pglz2 PRESERVE (pglz1, pglz);
+SELECT pg_column_compression('cmaltertest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz, pglz1
+(1 row)
+
+SELECT pg_column_compression('cmaltertest', 'f2');
+ pg_column_compression 
+-----------------------
+ pglz, pglz2, pglz1
+(1 row)
+
+-- make pglz2 droppable
+ALTER TABLE cmaltertest ALTER COLUMN f2 SET COMPRESSION pglz1;
+SELECT pg_column_compression('cmaltertest', 'f1');
+ pg_column_compression 
+-----------------------
+ pglz, pglz1
+(1 row)
+
+SELECT pg_column_compression('cmaltertest', 'f2');
+ pg_column_compression 
+-----------------------
+ pglz1
+(1 row)
+
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass OR acrelid = 'cmtest'::regclass;
+ acname | acattnum |       acoptions       
+--------+----------+-----------------------
+ pglz1  |        1 | 
+ pglz   |        1 | {min_input_size=1000}
+ pglz1  |        2 | 
+ pglz1  |        1 | 
+(4 rows)
+
+-- zlib compression
+CREATE TABLE zlibtest(f1 TEXT COMPRESSION zlib WITH (invalid 'param'));
+ERROR:  not built with zlib support
+CREATE TABLE zlibtest(f1 TEXT COMPRESSION zlib WITH (level 'best'));
+ERROR:  not built with zlib support
+CREATE TABLE zlibtest(f1 TEXT COMPRESSION zlib);
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (level 'best_compression');
+ERROR:  not built with zlib support
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (level 'best_speed');
+ERROR:  not built with zlib support
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (level 'default');
+ERROR:  not built with zlib support
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (dict 'one');
+ERROR:  not built with zlib support
+INSERT INTO zlibtest VALUES(repeat('1234567890',1004));
+ERROR:  not built with zlib support
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (dict 'one two') PRESERVE (zlib);
+ERROR:  not built with zlib support
+INSERT INTO zlibtest VALUES(repeat('1234567890 one two three',1004));
+ERROR:  not built with zlib support
+SELECT length(f1) FROM zlibtest;
+ length 
+--------
+(0 rows)
+
+DROP ACCESS METHOD pglz2;
+DROP VIEW cmtest_deps;
+DROP TABLE cmmove1, cmmove2, cmmove3;
+DROP TABLE cmexample, cmtestlike1, cmtestlike2, zlibtest;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index d51e547278..bd84883175 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -445,11 +445,11 @@ Partition key: RANGE (a oid_ops, plusone(b), c, d COLLATE "C")
 Number of partitions: 0
 
 \d+ partitioned2
-                          Partitioned table "public.partitioned2"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | integer |           |          |         | plain    |              | 
- b      | text    |           |          |         | extended |              | 
+                                 Partitioned table "public.partitioned2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | integer |           |          |         | plain    |             |              | 
+ b      | text    |           |          |         | extended | pglz        |              | 
 Partition key: RANGE (((a + 1)), substr(b, 1, 5))
 Number of partitions: 0
 
@@ -458,11 +458,11 @@ ERROR:  no partition of relation "partitioned2" found for row
 DETAIL:  Partition key of the failing row contains ((a + 1), substr(b, 1, 5)) = (2, hello).
 CREATE TABLE part2_1 PARTITION OF partitioned2 FOR VALUES FROM (-1, 'aaaaa') TO (100, 'ccccc');
 \d+ part2_1
-                                  Table "public.part2_1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | integer |           |          |         | plain    |              | 
- b      | text    |           |          |         | extended |              | 
+                                         Table "public.part2_1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | integer |           |          |         | plain    |             |              | 
+ b      | text    |           |          |         | extended | pglz        |              | 
 Partition of: partitioned2 FOR VALUES FROM ('-1', 'aaaaa') TO (100, 'ccccc')
 Partition constraint: (((a + 1) IS NOT NULL) AND (substr(b, 1, 5) IS NOT NULL) AND (((a + 1) > '-1'::integer) OR (((a + 1) = '-1'::integer) AND (substr(b, 1, 5) >= 'aaaaa'::text))) AND (((a + 1) < 100) OR (((a + 1) = 100) AND (substr(b, 1, 5) < 'ccccc'::text))))
 
@@ -479,10 +479,10 @@ CREATE TABLE part_p2 PARTITION OF list_parted FOR VALUES IN (2);
 CREATE TABLE part_p3 PARTITION OF list_parted FOR VALUES IN ((2+1));
 CREATE TABLE part_null PARTITION OF list_parted FOR VALUES IN (null);
 \d+ list_parted
-                          Partitioned table "public.list_parted"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- a      | integer |           |          |         | plain   |              | 
+                                 Partitioned table "public.list_parted"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ a      | integer |           |          |         | plain   |             |              | 
 Partition key: LIST (a)
 Partitions: part_null FOR VALUES IN (NULL),
             part_p1 FOR VALUES IN (1),
@@ -832,21 +832,21 @@ create table test_part_coll_cast2 partition of test_part_coll_posix for values f
 drop table test_part_coll_posix;
 -- Partition bound in describe output
 \d+ part_b
-                                   Table "public.part_b"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           | not null | 1       | plain    |              | 
+                                          Table "public.part_b"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           | not null | 1       | plain    |             |              | 
 Partition of: parted FOR VALUES IN ('b')
 Partition constraint: ((a IS NOT NULL) AND (a = 'b'::text))
 
 -- Both partition bound and partition key in describe output
 \d+ part_c
-                             Partitioned table "public.part_c"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           | not null | 0       | plain    |              | 
+                                    Partitioned table "public.part_c"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           | not null | 0       | plain    |             |              | 
 Partition of: parted FOR VALUES IN ('c')
 Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text))
 Partition key: RANGE (b)
@@ -854,11 +854,11 @@ Partitions: part_c_1_10 FOR VALUES FROM (1) TO (10)
 
 -- a level-2 partition's constraint will include the parent's expressions
 \d+ part_c_1_10
-                                Table "public.part_c_1_10"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           | not null | 0       | plain    |              | 
+                                       Table "public.part_c_1_10"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           | not null | 0       | plain    |             |              | 
 Partition of: part_c FOR VALUES FROM (1) TO (10)
 Partition constraint: ((a IS NOT NULL) AND (a = 'c'::text) AND (b IS NOT NULL) AND (b >= 1) AND (b < 10))
 
@@ -887,46 +887,46 @@ Number of partitions: 3 (Use \d+ to list them.)
 CREATE TABLE range_parted4 (a int, b int, c int) PARTITION BY RANGE (abs(a), abs(b), c);
 CREATE TABLE unbounded_range_part PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE);
 \d+ unbounded_range_part
-                           Table "public.unbounded_range_part"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- a      | integer |           |          |         | plain   |              | 
- b      | integer |           |          |         | plain   |              | 
- c      | integer |           |          |         | plain   |              | 
+                                  Table "public.unbounded_range_part"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ a      | integer |           |          |         | plain   |             |              | 
+ b      | integer |           |          |         | plain   |             |              | 
+ c      | integer |           |          |         | plain   |             |              | 
 Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (MAXVALUE, MAXVALUE, MAXVALUE)
 Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL))
 
 DROP TABLE unbounded_range_part;
 CREATE TABLE range_parted4_1 PARTITION OF range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE);
 \d+ range_parted4_1
-                              Table "public.range_parted4_1"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- a      | integer |           |          |         | plain   |              | 
- b      | integer |           |          |         | plain   |              | 
- c      | integer |           |          |         | plain   |              | 
+                                     Table "public.range_parted4_1"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ a      | integer |           |          |         | plain   |             |              | 
+ b      | integer |           |          |         | plain   |             |              | 
+ c      | integer |           |          |         | plain   |             |              | 
 Partition of: range_parted4 FOR VALUES FROM (MINVALUE, MINVALUE, MINVALUE) TO (1, MAXVALUE, MAXVALUE)
 Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND (abs(a) <= 1))
 
 CREATE TABLE range_parted4_2 PARTITION OF range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE);
 \d+ range_parted4_2
-                              Table "public.range_parted4_2"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- a      | integer |           |          |         | plain   |              | 
- b      | integer |           |          |         | plain   |              | 
- c      | integer |           |          |         | plain   |              | 
+                                     Table "public.range_parted4_2"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ a      | integer |           |          |         | plain   |             |              | 
+ b      | integer |           |          |         | plain   |             |              | 
+ c      | integer |           |          |         | plain   |             |              | 
 Partition of: range_parted4 FOR VALUES FROM (3, 4, 5) TO (6, 7, MAXVALUE)
 Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 3) OR ((abs(a) = 3) AND (abs(b) > 4)) OR ((abs(a) = 3) AND (abs(b) = 4) AND (c >= 5))) AND ((abs(a) < 6) OR ((abs(a) = 6) AND (abs(b) <= 7))))
 
 CREATE TABLE range_parted4_3 PARTITION OF range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE);
 \d+ range_parted4_3
-                              Table "public.range_parted4_3"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- a      | integer |           |          |         | plain   |              | 
- b      | integer |           |          |         | plain   |              | 
- c      | integer |           |          |         | plain   |              | 
+                                     Table "public.range_parted4_3"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ a      | integer |           |          |         | plain   |             |              | 
+ b      | integer |           |          |         | plain   |             |              | 
+ c      | integer |           |          |         | plain   |             |              | 
 Partition of: range_parted4 FOR VALUES FROM (6, 8, MINVALUE) TO (9, MAXVALUE, MAXVALUE)
 Partition constraint: ((abs(a) IS NOT NULL) AND (abs(b) IS NOT NULL) AND (c IS NOT NULL) AND ((abs(a) > 6) OR ((abs(a) = 6) AND (abs(b) >= 8))) AND (abs(a) <= 9))
 
@@ -958,11 +958,11 @@ SELECT obj_description('parted_col_comment'::regclass);
 (1 row)
 
 \d+ parted_col_comment
-                        Partitioned table "public.parted_col_comment"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target |  Description  
---------+---------+-----------+----------+---------+----------+--------------+---------------
- a      | integer |           |          |         | plain    |              | Partition key
- b      | text    |           |          |         | extended |              | 
+                               Partitioned table "public.parted_col_comment"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target |  Description  
+--------+---------+-----------+----------+---------+----------+-------------+--------------+---------------
+ a      | integer |           |          |         | plain    |             |              | Partition key
+ b      | text    |           |          |         | extended | pglz        |              | 
 Partition key: LIST (a)
 Number of partitions: 0
 
@@ -971,10 +971,10 @@ DROP TABLE parted_col_comment;
 CREATE TABLE arrlp (a int[]) PARTITION BY LIST (a);
 CREATE TABLE arrlp12 PARTITION OF arrlp FOR VALUES IN ('{1}', '{2}');
 \d+ arrlp12
-                                   Table "public.arrlp12"
- Column |   Type    | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+-----------+-----------+----------+---------+----------+--------------+-------------
- a      | integer[] |           |          |         | extended |              | 
+                                          Table "public.arrlp12"
+ Column |   Type    | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+-----------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | integer[] |           |          |         | extended | pglz        |              | 
 Partition of: arrlp FOR VALUES IN ('{1}', '{2}')
 Partition constraint: ((a IS NOT NULL) AND ((a = '{1}'::integer[]) OR (a = '{2}'::integer[])))
 
@@ -984,10 +984,10 @@ create table boolspart (a bool) partition by list (a);
 create table boolspart_t partition of boolspart for values in (true);
 create table boolspart_f partition of boolspart for values in (false);
 \d+ boolspart
-                           Partitioned table "public.boolspart"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- a      | boolean |           |          |         | plain   |              | 
+                                  Partitioned table "public.boolspart"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ a      | boolean |           |          |         | plain   |             |              | 
 Partition key: LIST (a)
 Partitions: boolspart_f FOR VALUES IN (false),
             boolspart_t FOR VALUES IN (true)
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index b582211270..d830ce830c 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -158,32 +158,32 @@ CREATE TABLE ctlt4 (a text, c text);
 ALTER TABLE ctlt4 ALTER COLUMN c SET STORAGE EXTERNAL;
 CREATE TABLE ctlt12_storage (LIKE ctlt1 INCLUDING STORAGE, LIKE ctlt2 INCLUDING STORAGE);
 \d+ ctlt12_storage
-                             Table "public.ctlt12_storage"
- Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+------+-----------+----------+---------+----------+--------------+-------------
- a      | text |           | not null |         | main     |              | 
- b      | text |           |          |         | extended |              | 
- c      | text |           |          |         | external |              | 
+                                    Table "public.ctlt12_storage"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text |           | not null |         | main     | pglz        |              | 
+ b      | text |           |          |         | extended | pglz        |              | 
+ c      | text |           |          |         | external | pglz        |              | 
 
 CREATE TABLE ctlt12_comments (LIKE ctlt1 INCLUDING COMMENTS, LIKE ctlt2 INCLUDING COMMENTS);
 \d+ ctlt12_comments
-                             Table "public.ctlt12_comments"
- Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+------+-----------+----------+---------+----------+--------------+-------------
- a      | text |           | not null |         | extended |              | A
- b      | text |           |          |         | extended |              | B
- c      | text |           |          |         | extended |              | C
+                                    Table "public.ctlt12_comments"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text |           | not null |         | extended | pglz        |              | A
+ b      | text |           |          |         | extended | pglz        |              | B
+ c      | text |           |          |         | extended | pglz        |              | C
 
 CREATE TABLE ctlt1_inh (LIKE ctlt1 INCLUDING CONSTRAINTS INCLUDING COMMENTS) INHERITS (ctlt1);
 NOTICE:  merging column "a" with inherited definition
 NOTICE:  merging column "b" with inherited definition
 NOTICE:  merging constraint "ctlt1_a_check" with inherited definition
 \d+ ctlt1_inh
-                                Table "public.ctlt1_inh"
- Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+------+-----------+----------+---------+----------+--------------+-------------
- a      | text |           | not null |         | main     |              | A
- b      | text |           |          |         | extended |              | B
+                                       Table "public.ctlt1_inh"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text |           | not null |         | main     | pglz        |              | A
+ b      | text |           |          |         | extended | pglz        |              | B
 Check constraints:
     "ctlt1_a_check" CHECK (length(a) > 2)
 Inherits: ctlt1
@@ -197,12 +197,12 @@ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_con
 CREATE TABLE ctlt13_inh () INHERITS (ctlt1, ctlt3);
 NOTICE:  merging multiple inherited definitions of column "a"
 \d+ ctlt13_inh
-                               Table "public.ctlt13_inh"
- Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+------+-----------+----------+---------+----------+--------------+-------------
- a      | text |           | not null |         | main     |              | 
- b      | text |           |          |         | extended |              | 
- c      | text |           |          |         | external |              | 
+                                      Table "public.ctlt13_inh"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text |           | not null |         | main     | pglz        |              | 
+ b      | text |           |          |         | extended | pglz        |              | 
+ c      | text |           |          |         | external | pglz        |              | 
 Check constraints:
     "ctlt1_a_check" CHECK (length(a) > 2)
     "ctlt3_a_check" CHECK (length(a) < 5)
@@ -212,12 +212,12 @@ Inherits: ctlt1,
 CREATE TABLE ctlt13_like (LIKE ctlt3 INCLUDING CONSTRAINTS INCLUDING COMMENTS INCLUDING STORAGE) INHERITS (ctlt1);
 NOTICE:  merging column "a" with inherited definition
 \d+ ctlt13_like
-                               Table "public.ctlt13_like"
- Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+------+-----------+----------+---------+----------+--------------+-------------
- a      | text |           | not null |         | main     |              | A3
- b      | text |           |          |         | extended |              | 
- c      | text |           |          |         | external |              | C
+                                      Table "public.ctlt13_like"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text |           | not null |         | main     | pglz        |              | A3
+ b      | text |           |          |         | extended | pglz        |              | 
+ c      | text |           |          |         | external | pglz        |              | C
 Check constraints:
     "ctlt1_a_check" CHECK (length(a) > 2)
     "ctlt3_a_check" CHECK (length(a) < 5)
@@ -231,11 +231,11 @@ SELECT description FROM pg_description, pg_constraint c WHERE classoid = 'pg_con
 
 CREATE TABLE ctlt_all (LIKE ctlt1 INCLUDING ALL);
 \d+ ctlt_all
-                                Table "public.ctlt_all"
- Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+------+-----------+----------+---------+----------+--------------+-------------
- a      | text |           | not null |         | main     |              | A
- b      | text |           |          |         | extended |              | B
+                                       Table "public.ctlt_all"
+ Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text |           | not null |         | main     | pglz        |              | A
+ b      | text |           |          |         | extended | pglz        |              | B
 Indexes:
     "ctlt_all_pkey" PRIMARY KEY, btree (a)
     "ctlt_all_b_idx" btree (b)
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 4ff1b4af41..5255951060 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -272,10 +272,10 @@ explain (verbose, costs off)
 create rule silly as on delete to dcomptable do instead
   update dcomptable set d1.r = (d1).r - 1, d1.i = (d1).i + 1 where (d1).i > 0;
 \d+ dcomptable
-                                  Table "public.dcomptable"
- Column |   Type    | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+-----------+-----------+----------+---------+----------+--------------+-------------
- d1     | dcomptype |           |          |         | extended |              | 
+                                         Table "public.dcomptable"
+ Column |   Type    | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+-----------+-----------+----------+---------+----------+-------------+--------------+-------------
+ d1     | dcomptype |           |          |         | extended | pglz        |              | 
 Indexes:
     "dcomptable_d1_key" UNIQUE CONSTRAINT, btree (d1)
 Rules:
@@ -409,10 +409,10 @@ create rule silly as on delete to dcomptable do instead
   update dcomptable set d1[1].r = d1[1].r - 1, d1[1].i = d1[1].i + 1
     where d1[1].i > 0;
 \d+ dcomptable
-                                  Table "public.dcomptable"
- Column |    Type    | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+------------+-----------+----------+---------+----------+--------------+-------------
- d1     | dcomptypea |           |          |         | extended |              | 
+                                         Table "public.dcomptable"
+ Column |    Type    | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+------------+-----------+----------+---------+----------+-------------+--------------+-------------
+ d1     | dcomptypea |           |          |         | extended | pglz        |              | 
 Indexes:
     "dcomptable_d1_key" UNIQUE CONSTRAINT, btree (d1)
 Rules:
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 4d82d3a7e8..8c8a03cfc2 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -1332,12 +1332,12 @@ CREATE TABLE fd_pt1 (
 CREATE FOREIGN TABLE ft2 () INHERITS (fd_pt1)
   SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Child tables: ft2
 
 \d+ ft2
@@ -1353,12 +1353,12 @@ Inherits: fd_pt1
 
 DROP FOREIGN TABLE ft2;
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 
 CREATE FOREIGN TABLE ft2 (
 	c1 integer NOT NULL,
@@ -1377,12 +1377,12 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 
 ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Child tables: ft2
 
 \d+ ft2
@@ -1420,12 +1420,12 @@ Child tables: ct3,
               ft3
 
 \d+ ct3
-                                    Table "public.ct3"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                           Table "public.ct3"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Inherits: ft2
 
 \d+ ft3
@@ -1445,17 +1445,17 @@ ALTER TABLE fd_pt1 ADD COLUMN c6 integer;
 ALTER TABLE fd_pt1 ADD COLUMN c7 integer NOT NULL;
 ALTER TABLE fd_pt1 ADD COLUMN c8 integer;
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
- c4     | integer |           |          |         | plain    |              | 
- c5     | integer |           |          | 0       | plain    |              | 
- c6     | integer |           |          |         | plain    |              | 
- c7     | integer |           | not null |         | plain    |              | 
- c8     | integer |           |          |         | plain    |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
+ c4     | integer |           |          |         | plain    |             |              | 
+ c5     | integer |           |          | 0       | plain    |             |              | 
+ c6     | integer |           |          |         | plain    |             |              | 
+ c7     | integer |           | not null |         | plain    |             |              | 
+ c8     | integer |           |          |         | plain    |             |              | 
 Child tables: ft2
 
 \d+ ft2
@@ -1477,17 +1477,17 @@ Child tables: ct3,
               ft3
 
 \d+ ct3
-                                    Table "public.ct3"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
- c4     | integer |           |          |         | plain    |              | 
- c5     | integer |           |          | 0       | plain    |              | 
- c6     | integer |           |          |         | plain    |              | 
- c7     | integer |           | not null |         | plain    |              | 
- c8     | integer |           |          |         | plain    |              | 
+                                           Table "public.ct3"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
+ c4     | integer |           |          |         | plain    |             |              | 
+ c5     | integer |           |          | 0       | plain    |             |              | 
+ c6     | integer |           |          |         | plain    |             |              | 
+ c7     | integer |           | not null |         | plain    |             |              | 
+ c8     | integer |           |          |         | plain    |             |              | 
 Inherits: ft2
 
 \d+ ft3
@@ -1519,17 +1519,17 @@ ALTER TABLE fd_pt1 ALTER COLUMN c1 SET (n_distinct = 100);
 ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STATISTICS -1;
 ALTER TABLE fd_pt1 ALTER COLUMN c8 SET STORAGE EXTERNAL;
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    | 10000        | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
- c4     | integer |           |          | 0       | plain    |              | 
- c5     | integer |           |          |         | plain    |              | 
- c6     | integer |           | not null |         | plain    |              | 
- c7     | integer |           |          |         | plain    |              | 
- c8     | text    |           |          |         | external |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             | 10000        | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
+ c4     | integer |           |          | 0       | plain    |             |              | 
+ c5     | integer |           |          |         | plain    |             |              | 
+ c6     | integer |           | not null |         | plain    |             |              | 
+ c7     | integer |           |          |         | plain    |             |              | 
+ c8     | text    |           |          |         | external | pglz        |              | 
 Child tables: ft2
 
 \d+ ft2
@@ -1557,12 +1557,12 @@ ALTER TABLE fd_pt1 DROP COLUMN c6;
 ALTER TABLE fd_pt1 DROP COLUMN c7;
 ALTER TABLE fd_pt1 DROP COLUMN c8;
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    | 10000        | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             | 10000        | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Child tables: ft2
 
 \d+ ft2
@@ -1594,12 +1594,12 @@ SELECT relname, conname, contype, conislocal, coninhcount, connoinherit
 
 -- child does not inherit NO INHERIT constraints
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    | 10000        | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             | 10000        | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Check constraints:
     "fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
     "fd_pt1chk2" CHECK (c2 <> ''::text)
@@ -1638,12 +1638,12 @@ ALTER FOREIGN TABLE ft2 ADD CONSTRAINT fd_pt1chk2 CHECK (c2 <> '');
 ALTER FOREIGN TABLE ft2 INHERIT fd_pt1;
 -- child does not inherit NO INHERIT constraints
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    | 10000        | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             | 10000        | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Check constraints:
     "fd_pt1chk1" CHECK (c1 > 0) NO INHERIT
     "fd_pt1chk2" CHECK (c2 <> ''::text)
@@ -1669,12 +1669,12 @@ ALTER TABLE fd_pt1 DROP CONSTRAINT fd_pt1chk2 CASCADE;
 INSERT INTO fd_pt1 VALUES (1, 'fd_pt1'::text, '1994-01-01'::date);
 ALTER TABLE fd_pt1 ADD CONSTRAINT fd_pt1chk3 CHECK (c2 <> '') NOT VALID;
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    | 10000        | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             | 10000        | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Check constraints:
     "fd_pt1chk3" CHECK (c2 <> ''::text) NOT VALID
 Child tables: ft2
@@ -1696,12 +1696,12 @@ Inherits: fd_pt1
 -- VALIDATE CONSTRAINT need do nothing on foreign tables
 ALTER TABLE fd_pt1 VALIDATE CONSTRAINT fd_pt1chk3;
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    | 10000        | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             | 10000        | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Check constraints:
     "fd_pt1chk3" CHECK (c2 <> ''::text)
 Child tables: ft2
@@ -1727,12 +1727,12 @@ ALTER TABLE fd_pt1 RENAME COLUMN c3 TO f3;
 -- changes name of a constraint recursively
 ALTER TABLE fd_pt1 RENAME CONSTRAINT fd_pt1chk3 TO f2_check;
 \d+ fd_pt1
-                                   Table "public.fd_pt1"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- f1     | integer |           | not null |         | plain    | 10000        | 
- f2     | text    |           |          |         | extended |              | 
- f3     | date    |           |          |         | plain    |              | 
+                                          Table "public.fd_pt1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1     | integer |           | not null |         | plain    |             | 10000        | 
+ f2     | text    |           |          |         | extended | pglz        |              | 
+ f3     | date    |           |          |         | plain    |             |              | 
 Check constraints:
     "f2_check" CHECK (f2 <> ''::text)
 Child tables: ft2
@@ -1791,12 +1791,12 @@ CREATE TABLE fd_pt2 (
 CREATE FOREIGN TABLE fd_pt2_1 PARTITION OF fd_pt2 FOR VALUES IN (1)
   SERVER s0 OPTIONS (delimiter ',', quote '"', "be quoted" 'value');
 \d+ fd_pt2
-                             Partitioned table "public.fd_pt2"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                    Partitioned table "public.fd_pt2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Partition key: LIST (c1)
 Partitions: fd_pt2_1 FOR VALUES IN (1)
 
@@ -1836,12 +1836,12 @@ ERROR:  table "fd_pt2_1" contains column "c4" not found in parent "fd_pt2"
 DETAIL:  The new partition may contain only the columns present in parent.
 DROP FOREIGN TABLE fd_pt2_1;
 \d+ fd_pt2
-                             Partitioned table "public.fd_pt2"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                    Partitioned table "public.fd_pt2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Partition key: LIST (c1)
 Number of partitions: 0
 
@@ -1863,12 +1863,12 @@ FDW options: (delimiter ',', quote '"', "be quoted" 'value')
 -- no attach partition validation occurs for foreign tables
 ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
 \d+ fd_pt2
-                             Partitioned table "public.fd_pt2"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                    Partitioned table "public.fd_pt2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Partition key: LIST (c1)
 Partitions: fd_pt2_1 FOR VALUES IN (1)
 
@@ -1891,12 +1891,12 @@ ERROR:  cannot add column to a partition
 ALTER TABLE fd_pt2_1 ALTER c3 SET NOT NULL;
 ALTER TABLE fd_pt2_1 ADD CONSTRAINT p21chk CHECK (c2 <> '');
 \d+ fd_pt2
-                             Partitioned table "public.fd_pt2"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           |          |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                    Partitioned table "public.fd_pt2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           |          |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Partition key: LIST (c1)
 Partitions: fd_pt2_1 FOR VALUES IN (1)
 
@@ -1921,12 +1921,12 @@ ERROR:  column "c1" is marked NOT NULL in parent table
 ALTER TABLE fd_pt2 DETACH PARTITION fd_pt2_1;
 ALTER TABLE fd_pt2 ALTER c2 SET NOT NULL;
 \d+ fd_pt2
-                             Partitioned table "public.fd_pt2"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           | not null |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                    Partitioned table "public.fd_pt2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           | not null |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Partition key: LIST (c1)
 Number of partitions: 0
 
@@ -1949,12 +1949,12 @@ ALTER TABLE fd_pt2 ATTACH PARTITION fd_pt2_1 FOR VALUES IN (1);
 ALTER TABLE fd_pt2 DETACH PARTITION fd_pt2_1;
 ALTER TABLE fd_pt2 ADD CONSTRAINT fd_pt2chk1 CHECK (c1 > 0);
 \d+ fd_pt2
-                             Partitioned table "public.fd_pt2"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- c1     | integer |           | not null |         | plain    |              | 
- c2     | text    |           | not null |         | extended |              | 
- c3     | date    |           |          |         | plain    |              | 
+                                    Partitioned table "public.fd_pt2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ c1     | integer |           | not null |         | plain    |             |              | 
+ c2     | text    |           | not null |         | extended | pglz        |              | 
+ c3     | date    |           |          |         | plain    |             |              | 
 Partition key: LIST (c1)
 Check constraints:
     "fd_pt2chk1" CHECK (c1 > 0)
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 565d947b6d..285e36fb21 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1033,13 +1033,13 @@ ALTER TABLE inhts RENAME aa TO aaa;      -- to be failed
 ERROR:  cannot rename inherited column "aa"
 ALTER TABLE inhts RENAME d TO dd;
 \d+ inhts
-                                   Table "public.inhts"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- aa     | integer |           |          |         | plain   |              | 
- b      | integer |           |          |         | plain   |              | 
- c      | integer |           |          |         | plain   |              | 
- dd     | integer |           |          |         | plain   |              | 
+                                          Table "public.inhts"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ aa     | integer |           |          |         | plain   |             |              | 
+ b      | integer |           |          |         | plain   |             |              | 
+ c      | integer |           |          |         | plain   |             |              | 
+ dd     | integer |           |          |         | plain   |             |              | 
 Inherits: inht1,
           inhs1
 
@@ -1052,14 +1052,14 @@ NOTICE:  merging multiple inherited definitions of column "aa"
 NOTICE:  merging multiple inherited definitions of column "b"
 ALTER TABLE inht1 RENAME aa TO aaa;
 \d+ inht4
-                                   Table "public.inht4"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- aaa    | integer |           |          |         | plain   |              | 
- b      | integer |           |          |         | plain   |              | 
- x      | integer |           |          |         | plain   |              | 
- y      | integer |           |          |         | plain   |              | 
- z      | integer |           |          |         | plain   |              | 
+                                          Table "public.inht4"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ aaa    | integer |           |          |         | plain   |             |              | 
+ b      | integer |           |          |         | plain   |             |              | 
+ x      | integer |           |          |         | plain   |             |              | 
+ y      | integer |           |          |         | plain   |             |              | 
+ z      | integer |           |          |         | plain   |             |              | 
 Inherits: inht2,
           inht3
 
@@ -1069,14 +1069,14 @@ ALTER TABLE inht1 RENAME aaa TO aaaa;
 ALTER TABLE inht1 RENAME b TO bb;                -- to be failed
 ERROR:  cannot rename inherited column "b"
 \d+ inhts
-                                   Table "public.inhts"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- aaaa   | integer |           |          |         | plain   |              | 
- b      | integer |           |          |         | plain   |              | 
- x      | integer |           |          |         | plain   |              | 
- c      | integer |           |          |         | plain   |              | 
- d      | integer |           |          |         | plain   |              | 
+                                          Table "public.inhts"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ aaaa   | integer |           |          |         | plain   |             |              | 
+ b      | integer |           |          |         | plain   |             |              | 
+ x      | integer |           |          |         | plain   |             |              | 
+ c      | integer |           |          |         | plain   |             |              | 
+ d      | integer |           |          |         | plain   |             |              | 
 Inherits: inht2,
           inhs1
 
@@ -1116,33 +1116,33 @@ drop cascades to table inht4
 CREATE TABLE test_constraints (id int, val1 varchar, val2 int, UNIQUE(val1, val2));
 CREATE TABLE test_constraints_inh () INHERITS (test_constraints);
 \d+ test_constraints
-                                   Table "public.test_constraints"
- Column |       Type        | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+-------------------+-----------+----------+---------+----------+--------------+-------------
- id     | integer           |           |          |         | plain    |              | 
- val1   | character varying |           |          |         | extended |              | 
- val2   | integer           |           |          |         | plain    |              | 
+                                          Table "public.test_constraints"
+ Column |       Type        | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
+ id     | integer           |           |          |         | plain    |             |              | 
+ val1   | character varying |           |          |         | extended | pglz        |              | 
+ val2   | integer           |           |          |         | plain    |             |              | 
 Indexes:
     "test_constraints_val1_val2_key" UNIQUE CONSTRAINT, btree (val1, val2)
 Child tables: test_constraints_inh
 
 ALTER TABLE ONLY test_constraints DROP CONSTRAINT test_constraints_val1_val2_key;
 \d+ test_constraints
-                                   Table "public.test_constraints"
- Column |       Type        | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+-------------------+-----------+----------+---------+----------+--------------+-------------
- id     | integer           |           |          |         | plain    |              | 
- val1   | character varying |           |          |         | extended |              | 
- val2   | integer           |           |          |         | plain    |              | 
+                                          Table "public.test_constraints"
+ Column |       Type        | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
+ id     | integer           |           |          |         | plain    |             |              | 
+ val1   | character varying |           |          |         | extended | pglz        |              | 
+ val2   | integer           |           |          |         | plain    |             |              | 
 Child tables: test_constraints_inh
 
 \d+ test_constraints_inh
-                                 Table "public.test_constraints_inh"
- Column |       Type        | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+-------------------+-----------+----------+---------+----------+--------------+-------------
- id     | integer           |           |          |         | plain    |              | 
- val1   | character varying |           |          |         | extended |              | 
- val2   | integer           |           |          |         | plain    |              | 
+                                        Table "public.test_constraints_inh"
+ Column |       Type        | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
+ id     | integer           |           |          |         | plain    |             |              | 
+ val1   | character varying |           |          |         | extended | pglz        |              | 
+ val2   | integer           |           |          |         | plain    |             |              | 
 Inherits: test_constraints
 
 DROP TABLE test_constraints_inh;
@@ -1153,27 +1153,27 @@ CREATE TABLE test_ex_constraints (
 );
 CREATE TABLE test_ex_constraints_inh () INHERITS (test_ex_constraints);
 \d+ test_ex_constraints
-                           Table "public.test_ex_constraints"
- Column |  Type  | Collation | Nullable | Default | Storage | Stats target | Description 
---------+--------+-----------+----------+---------+---------+--------------+-------------
- c      | circle |           |          |         | plain   |              | 
+                                  Table "public.test_ex_constraints"
+ Column |  Type  | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+--------+-----------+----------+---------+---------+-------------+--------------+-------------
+ c      | circle |           |          |         | plain   |             |              | 
 Indexes:
     "test_ex_constraints_c_excl" EXCLUDE USING gist (c WITH &&)
 Child tables: test_ex_constraints_inh
 
 ALTER TABLE test_ex_constraints DROP CONSTRAINT test_ex_constraints_c_excl;
 \d+ test_ex_constraints
-                           Table "public.test_ex_constraints"
- Column |  Type  | Collation | Nullable | Default | Storage | Stats target | Description 
---------+--------+-----------+----------+---------+---------+--------------+-------------
- c      | circle |           |          |         | plain   |              | 
+                                  Table "public.test_ex_constraints"
+ Column |  Type  | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+--------+-----------+----------+---------+---------+-------------+--------------+-------------
+ c      | circle |           |          |         | plain   |             |              | 
 Child tables: test_ex_constraints_inh
 
 \d+ test_ex_constraints_inh
-                         Table "public.test_ex_constraints_inh"
- Column |  Type  | Collation | Nullable | Default | Storage | Stats target | Description 
---------+--------+-----------+----------+---------+---------+--------------+-------------
- c      | circle |           |          |         | plain   |              | 
+                                Table "public.test_ex_constraints_inh"
+ Column |  Type  | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+--------+-----------+----------+---------+---------+-------------+--------------+-------------
+ c      | circle |           |          |         | plain   |             |              | 
 Inherits: test_ex_constraints
 
 DROP TABLE test_ex_constraints_inh;
@@ -1183,37 +1183,37 @@ CREATE TABLE test_primary_constraints(id int PRIMARY KEY);
 CREATE TABLE test_foreign_constraints(id1 int REFERENCES test_primary_constraints(id));
 CREATE TABLE test_foreign_constraints_inh () INHERITS (test_foreign_constraints);
 \d+ test_primary_constraints
-                         Table "public.test_primary_constraints"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- id     | integer |           | not null |         | plain   |              | 
+                                Table "public.test_primary_constraints"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ id     | integer |           | not null |         | plain   |             |              | 
 Indexes:
     "test_primary_constraints_pkey" PRIMARY KEY, btree (id)
 Referenced by:
     TABLE "test_foreign_constraints" CONSTRAINT "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
 
 \d+ test_foreign_constraints
-                         Table "public.test_foreign_constraints"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- id1    | integer |           |          |         | plain   |              | 
+                                Table "public.test_foreign_constraints"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ id1    | integer |           |          |         | plain   |             |              | 
 Foreign-key constraints:
     "test_foreign_constraints_id1_fkey" FOREIGN KEY (id1) REFERENCES test_primary_constraints(id)
 Child tables: test_foreign_constraints_inh
 
 ALTER TABLE test_foreign_constraints DROP CONSTRAINT test_foreign_constraints_id1_fkey;
 \d+ test_foreign_constraints
-                         Table "public.test_foreign_constraints"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- id1    | integer |           |          |         | plain   |              | 
+                                Table "public.test_foreign_constraints"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ id1    | integer |           |          |         | plain   |             |              | 
 Child tables: test_foreign_constraints_inh
 
 \d+ test_foreign_constraints_inh
-                       Table "public.test_foreign_constraints_inh"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- id1    | integer |           |          |         | plain   |              | 
+                              Table "public.test_foreign_constraints_inh"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ id1    | integer |           |          |         | plain   |             |              | 
 Inherits: test_foreign_constraints
 
 DROP TABLE test_foreign_constraints_inh;
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index 1cf6531c01..37f96ea7a2 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -142,11 +142,11 @@ create rule irule3 as on insert to inserttest2 do also
   insert into inserttest (f4[1].if1, f4[1].if2[2])
   select new.f1, new.f2;
 \d+ inserttest2
-                                Table "public.inserttest2"
- Column |  Type  | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+--------+-----------+----------+---------+----------+--------------+-------------
- f1     | bigint |           |          |         | plain    |              | 
- f2     | text   |           |          |         | extended |              | 
+                                       Table "public.inserttest2"
+ Column |  Type  | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+--------+-----------+----------+---------+----------+-------------+--------------+-------------
+ f1     | bigint |           |          |         | plain    |             |              | 
+ f2     | text   |           |          |         | extended | pglz        |              | 
 Rules:
     irule1 AS
     ON INSERT TO inserttest2 DO  INSERT INTO inserttest (f3.if2[1], f3.if2[2])
@@ -448,11 +448,11 @@ from hash_parted order by part;
 -- test \d+ output on a table which has both partitioned and unpartitioned
 -- partitions
 \d+ list_parted
-                          Partitioned table "public.list_parted"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           |          |         | plain    |              | 
+                                 Partitioned table "public.list_parted"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           |          |         | plain    |             |              | 
 Partition key: LIST (lower(a))
 Partitions: part_aa_bb FOR VALUES IN ('aa', 'bb'),
             part_cc_dd FOR VALUES IN ('cc', 'dd'),
@@ -470,10 +470,10 @@ drop table hash_parted;
 create table list_parted (a int) partition by list (a);
 create table part_default partition of list_parted default;
 \d+ part_default
-                               Table "public.part_default"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- a      | integer |           |          |         | plain   |              | 
+                                      Table "public.part_default"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ a      | integer |           |          |         | plain   |             |              | 
 Partition of: list_parted DEFAULT
 No partition constraint
 
@@ -799,11 +799,11 @@ create table mcrparted6_common_ge_10 partition of mcrparted for values from ('co
 create table mcrparted7_gt_common_lt_d partition of mcrparted for values from ('common', maxvalue) to ('d', minvalue);
 create table mcrparted8_ge_d partition of mcrparted for values from ('d', minvalue) to (maxvalue, maxvalue);
 \d+ mcrparted
-                           Partitioned table "public.mcrparted"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           |          |         | plain    |              | 
+                                  Partitioned table "public.mcrparted"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           |          |         | plain    |             |              | 
 Partition key: RANGE (a, b)
 Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE),
             mcrparted2_b FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE),
@@ -815,74 +815,74 @@ Partitions: mcrparted1_lt_b FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVAL
             mcrparted8_ge_d FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
 
 \d+ mcrparted1_lt_b
-                              Table "public.mcrparted1_lt_b"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           |          |         | plain    |              | 
+                                     Table "public.mcrparted1_lt_b"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           |          |         | plain    |             |              | 
 Partition of: mcrparted FOR VALUES FROM (MINVALUE, MINVALUE) TO ('b', MINVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a < 'b'::text))
 
 \d+ mcrparted2_b
-                                Table "public.mcrparted2_b"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           |          |         | plain    |              | 
+                                       Table "public.mcrparted2_b"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           |          |         | plain    |             |              | 
 Partition of: mcrparted FOR VALUES FROM ('b', MINVALUE) TO ('c', MINVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'b'::text) AND (a < 'c'::text))
 
 \d+ mcrparted3_c_to_common
-                           Table "public.mcrparted3_c_to_common"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           |          |         | plain    |              | 
+                                  Table "public.mcrparted3_c_to_common"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           |          |         | plain    |             |              | 
 Partition of: mcrparted FOR VALUES FROM ('c', MINVALUE) TO ('common', MINVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'c'::text) AND (a < 'common'::text))
 
 \d+ mcrparted4_common_lt_0
-                           Table "public.mcrparted4_common_lt_0"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           |          |         | plain    |              | 
+                                  Table "public.mcrparted4_common_lt_0"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           |          |         | plain    |             |              | 
 Partition of: mcrparted FOR VALUES FROM ('common', MINVALUE) TO ('common', 0)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b < 0))
 
 \d+ mcrparted5_common_0_to_10
-                         Table "public.mcrparted5_common_0_to_10"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           |          |         | plain    |              | 
+                                Table "public.mcrparted5_common_0_to_10"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           |          |         | plain    |             |              | 
 Partition of: mcrparted FOR VALUES FROM ('common', 0) TO ('common', 10)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 0) AND (b < 10))
 
 \d+ mcrparted6_common_ge_10
-                          Table "public.mcrparted6_common_ge_10"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           |          |         | plain    |              | 
+                                 Table "public.mcrparted6_common_ge_10"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           |          |         | plain    |             |              | 
 Partition of: mcrparted FOR VALUES FROM ('common', 10) TO ('common', MAXVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a = 'common'::text) AND (b >= 10))
 
 \d+ mcrparted7_gt_common_lt_d
-                         Table "public.mcrparted7_gt_common_lt_d"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           |          |         | plain    |              | 
+                                Table "public.mcrparted7_gt_common_lt_d"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           |          |         | plain    |             |              | 
 Partition of: mcrparted FOR VALUES FROM ('common', MAXVALUE) TO ('d', MINVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a > 'common'::text) AND (a < 'd'::text))
 
 \d+ mcrparted8_ge_d
-                              Table "public.mcrparted8_ge_d"
- Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------+----------+--------------+-------------
- a      | text    |           |          |         | extended |              | 
- b      | integer |           |          |         | plain    |              | 
+                                     Table "public.mcrparted8_ge_d"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text    |           |          |         | extended | pglz        |              | 
+ b      | integer |           |          |         | plain    |             |              | 
 Partition of: mcrparted FOR VALUES FROM ('d', MINVALUE) TO (MAXVALUE, MAXVALUE)
 Partition constraint: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a >= 'd'::text))
 
diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out
index ce25ee044a..d8cd5d8fde 100644
--- a/src/test/regress/expected/opr_sanity.out
+++ b/src/test/regress/expected/opr_sanity.out
@@ -1806,7 +1806,9 @@ WHERE p1.amhandler = 0;
 SELECT p1.oid, p1.amname, p2.oid, p2.proname
 FROM pg_am AS p1, pg_proc AS p2
 WHERE p2.oid = p1.amhandler AND
-    (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset
+    ((p2.prorettype != 'index_am_handler'::regtype
+      AND p2.prorettype != 'compression_am_handler'::regtype)
+     OR p2.proretset
      OR p2.pronargs != 1
      OR p2.proargtypes[0] != 'internal'::regtype);
  oid | amname | oid | proname 
diff --git a/src/test/regress/expected/publication.out b/src/test/regress/expected/publication.out
index afbbdd543d..4f386a84e7 100644
--- a/src/test/regress/expected/publication.out
+++ b/src/test/regress/expected/publication.out
@@ -65,11 +65,11 @@ SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_forall
 (1 row)
 
 \d+ testpub_tbl2
-                                                Table "public.testpub_tbl2"
- Column |  Type   | Collation | Nullable |                 Default                  | Storage  | Stats target | Description 
---------+---------+-----------+----------+------------------------------------------+----------+--------------+-------------
- id     | integer |           | not null | nextval('testpub_tbl2_id_seq'::regclass) | plain    |              | 
- data   | text    |           |          |                                          | extended |              | 
+                                                       Table "public.testpub_tbl2"
+ Column |  Type   | Collation | Nullable |                 Default                  | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+------------------------------------------+----------+-------------+--------------+-------------
+ id     | integer |           | not null | nextval('testpub_tbl2_id_seq'::regclass) | plain    |             |              | 
+ data   | text    |           |          |                                          | extended | pglz        |              | 
 Indexes:
     "testpub_tbl2_pkey" PRIMARY KEY, btree (id)
 Publications:
@@ -141,22 +141,22 @@ ALTER PUBLICATION testpub_default SET TABLE testpub_tbl1;
 ALTER PUBLICATION testpub_default ADD TABLE pub_test.testpub_nopk;
 ALTER PUBLICATION testpib_ins_trunct ADD TABLE pub_test.testpub_nopk, testpub_tbl1;
 \d+ pub_test.testpub_nopk
-                              Table "pub_test.testpub_nopk"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- foo    | integer |           |          |         | plain   |              | 
- bar    | integer |           |          |         | plain   |              | 
+                                     Table "pub_test.testpub_nopk"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ foo    | integer |           |          |         | plain   |             |              | 
+ bar    | integer |           |          |         | plain   |             |              | 
 Publications:
     "testpib_ins_trunct"
     "testpub_default"
     "testpub_fortbl"
 
 \d+ testpub_tbl1
-                                                Table "public.testpub_tbl1"
- Column |  Type   | Collation | Nullable |                 Default                  | Storage  | Stats target | Description 
---------+---------+-----------+----------+------------------------------------------+----------+--------------+-------------
- id     | integer |           | not null | nextval('testpub_tbl1_id_seq'::regclass) | plain    |              | 
- data   | text    |           |          |                                          | extended |              | 
+                                                       Table "public.testpub_tbl1"
+ Column |  Type   | Collation | Nullable |                 Default                  | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+------------------------------------------+----------+-------------+--------------+-------------
+ id     | integer |           | not null | nextval('testpub_tbl1_id_seq'::regclass) | plain    |             |              | 
+ data   | text    |           |          |                                          | extended | pglz        |              | 
 Indexes:
     "testpub_tbl1_pkey" PRIMARY KEY, btree (id)
 Publications:
@@ -178,11 +178,11 @@ ALTER PUBLICATION testpub_default DROP TABLE testpub_tbl1, pub_test.testpub_nopk
 ALTER PUBLICATION testpub_default DROP TABLE pub_test.testpub_nopk;
 ERROR:  relation "testpub_nopk" is not part of the publication
 \d+ testpub_tbl1
-                                                Table "public.testpub_tbl1"
- Column |  Type   | Collation | Nullable |                 Default                  | Storage  | Stats target | Description 
---------+---------+-----------+----------+------------------------------------------+----------+--------------+-------------
- id     | integer |           | not null | nextval('testpub_tbl1_id_seq'::regclass) | plain    |              | 
- data   | text    |           |          |                                          | extended |              | 
+                                                       Table "public.testpub_tbl1"
+ Column |  Type   | Collation | Nullable |                 Default                  | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+------------------------------------------+----------+-------------+--------------+-------------
+ id     | integer |           | not null | nextval('testpub_tbl1_id_seq'::regclass) | plain    |             |              | 
+ data   | text    |           |          |                                          | extended | pglz        |              | 
 Indexes:
     "testpub_tbl1_pkey" PRIMARY KEY, btree (id)
 Publications:
diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out
index 175ecd2879..7cf1927971 100644
--- a/src/test/regress/expected/replica_identity.out
+++ b/src/test/regress/expected/replica_identity.out
@@ -153,13 +153,13 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass;
 (1 row)
 
 \d+ test_replica_identity
-                                                Table "public.test_replica_identity"
- Column |  Type   | Collation | Nullable |                      Default                      | Storage  | Stats target | Description 
---------+---------+-----------+----------+---------------------------------------------------+----------+--------------+-------------
- id     | integer |           | not null | nextval('test_replica_identity_id_seq'::regclass) | plain    |              | 
- keya   | text    |           | not null |                                                   | extended |              | 
- keyb   | text    |           | not null |                                                   | extended |              | 
- nonkey | text    |           |          |                                                   | extended |              | 
+                                                       Table "public.test_replica_identity"
+ Column |  Type   | Collation | Nullable |                      Default                      | Storage  | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------------------------------------------------+----------+-------------+--------------+-------------
+ id     | integer |           | not null | nextval('test_replica_identity_id_seq'::regclass) | plain    |             |              | 
+ keya   | text    |           | not null |                                                   | extended | pglz        |              | 
+ keyb   | text    |           | not null |                                                   | extended | pglz        |              | 
+ nonkey | text    |           |          |                                                   | extended | pglz        |              | 
 Indexes:
     "test_replica_identity_pkey" PRIMARY KEY, btree (id)
     "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3))
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 2e170497c9..df49e98251 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -938,14 +938,14 @@ CREATE POLICY pp1 ON part_document AS PERMISSIVE
 CREATE POLICY pp1r ON part_document AS RESTRICTIVE TO regress_rls_dave
     USING (cid < 55);
 \d+ part_document
-                    Partitioned table "regress_rls_schema.part_document"
- Column  |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
----------+---------+-----------+----------+---------+----------+--------------+-------------
- did     | integer |           |          |         | plain    |              | 
- cid     | integer |           |          |         | plain    |              | 
- dlevel  | integer |           | not null |         | plain    |              | 
- dauthor | name    |           |          |         | plain    |              | 
- dtitle  | text    |           |          |         | extended |              | 
+                           Partitioned table "regress_rls_schema.part_document"
+ Column  |  Type   | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+---------+---------+-----------+----------+---------+----------+-------------+--------------+-------------
+ did     | integer |           |          |         | plain    |             |              | 
+ cid     | integer |           |          |         | plain    |             |              | 
+ dlevel  | integer |           | not null |         | plain    |             |              | 
+ dauthor | name    |           |          |         | plain    |             |              | 
+ dtitle  | text    |           |          |         | extended | pglz        |              | 
 Partition key: RANGE (cid)
 Policies:
     POLICY "pp1"
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 98f417cb57..a0b03839d5 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2814,11 +2814,11 @@ select * from rules_log;
 
 create rule r3 as on delete to rules_src do notify rules_src_deletion;
 \d+ rules_src
-                                 Table "public.rules_src"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- f1     | integer |           |          |         | plain   |              | 
- f2     | integer |           |          |         | plain   |              | 
+                                        Table "public.rules_src"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1     | integer |           |          |         | plain   |             |              | 
+ f2     | integer |           |          |         | plain   |             |              | 
 Rules:
     r1 AS
     ON UPDATE TO rules_src DO  INSERT INTO rules_log (f1, f2, tag) VALUES (old.f1,old.f2,'old'::text), (new.f1,new.f2,'new'::text)
@@ -2834,11 +2834,11 @@ Rules:
 create rule r4 as on insert to rules_src do instead insert into rules_log AS trgt SELECT NEW.* RETURNING trgt.f1, trgt.f2;
 create rule r5 as on update to rules_src do instead UPDATE rules_log AS trgt SET tag = 'updated' WHERE trgt.f1 = new.f1;
 \d+ rules_src
-                                 Table "public.rules_src"
- Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
---------+---------+-----------+----------+---------+---------+--------------+-------------
- f1     | integer |           |          |         | plain   |              | 
- f2     | integer |           |          |         | plain   |              | 
+                                        Table "public.rules_src"
+ Column |  Type   | Collation | Nullable | Default | Storage | Compression | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
+ f1     | integer |           |          |         | plain   |             |              | 
+ f2     | integer |           |          |         | plain   |             |              | 
 Rules:
     r1 AS
     ON UPDATE TO rules_src DO  INSERT INTO rules_log (f1, f2, tag) VALUES (old.f1,old.f2,'old'::text), (new.f1,new.f2,'new'::text)
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6cd937eb52..de8be73a54 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -32,6 +32,8 @@ check2_tbl|f
 check_tbl|f
 circle_tbl|t
 city|f
+cmaltertest|f
+cmtest|f
 copy_tbl|f
 d|f
 d_star|f
@@ -106,6 +108,7 @@ pg_aggregate|t
 pg_am|t
 pg_amop|t
 pg_amproc|t
+pg_attr_compression|t
 pg_attrdef|t
 pg_attribute|t
 pg_auth_members|t
diff --git a/src/test/regress/expected/update.out b/src/test/regress/expected/update.out
index 2083345c8e..b245059e37 100644
--- a/src/test/regress/expected/update.out
+++ b/src/test/regress/expected/update.out
@@ -690,14 +690,14 @@ DROP TRIGGER d15_insert_trig ON part_d_15_20;
 :init_range_parted;
 create table part_def partition of range_parted default;
 \d+ part_def
-                                       Table "public.part_def"
- Column |       Type        | Collation | Nullable | Default | Storage  | Stats target | Description 
---------+-------------------+-----------+----------+---------+----------+--------------+-------------
- a      | text              |           |          |         | extended |              | 
- b      | bigint            |           |          |         | plain    |              | 
- c      | numeric           |           |          |         | main     |              | 
- d      | integer           |           |          |         | plain    |              | 
- e      | character varying |           |          |         | extended |              | 
+                                              Table "public.part_def"
+ Column |       Type        | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
+--------+-------------------+-----------+----------+---------+----------+-------------+--------------+-------------
+ a      | text              |           |          |         | extended | pglz        |              | 
+ b      | bigint            |           |          |         | plain    |             |              | 
+ c      | numeric           |           |          |         | main     | pglz        |              | 
+ d      | integer           |           |          |         | plain    |             |              | 
+ e      | character varying |           |          |         | extended | pglz        |              | 
 Partition of: range_parted DEFAULT
 Partition constraint: (NOT ((a IS NOT NULL) AND (b IS NOT NULL) AND (((a = 'a'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'a'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '1'::bigint) AND (b < '10'::bigint)) OR ((a = 'b'::text) AND (b >= '10'::bigint) AND (b < '20'::bigint)) OR ((a = 'b'::text) AND (b >= '20'::bigint) AND (b < '30'::bigint)))))
 
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 4051a4ad4e..f591a981ce 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -41,6 +41,7 @@ test: create_function_1
 test: create_type
 test: create_table
 test: create_function_2
+test: create_cm
 
 # ----------
 # Load huge amounts of data
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index ac1ea622d6..ae4724e9e7 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -40,6 +40,7 @@ test: inet
 test: macaddr
 test: macaddr8
 test: tstypes
+test: create_cm
 test: geometry
 test: horology
 test: regex
diff --git a/src/test/regress/sql/create_cm.sql b/src/test/regress/sql/create_cm.sql
new file mode 100644
index 0000000000..d7e6c5eba2
--- /dev/null
+++ b/src/test/regress/sql/create_cm.sql
@@ -0,0 +1,203 @@
+-- test drop
+DROP ACCESS METHOD pglz; --fail
+
+CREATE ACCESS METHOD pglz1 TYPE COMPRESSION HANDLER pglzhandler;
+CREATE TABLE droptest(d1 TEXT COMPRESSION pglz1);
+DROP ACCESS METHOD pglz1;
+DROP ACCESS METHOD pglz1 CASCADE;
+\d+ droptest
+DROP TABLE droptest;
+
+CREATE ACCESS METHOD pglz1 TYPE COMPRESSION HANDLER pglzhandler;
+
+-- test auto drop of related attribute compression
+CREATE ACCESS METHOD pglz2 TYPE COMPRESSION HANDLER pglzhandler;
+CREATE TABLE cmaltertest (f1 TEXT COMPRESSION pglz2);
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass;
+ALTER TABLE cmaltertest DROP COLUMN f1;
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass;
+DROP TABLE cmaltertest;
+
+-- test drop
+DROP ACCESS METHOD pglz2;
+
+-- test alter data type
+CREATE TABLE cmaltertest(at1 TEXT);
+ALTER TABLE cmaltertest ALTER COLUMN at1 SET COMPRESSION pglz1 PRESERVE (pglz);
+SELECT pg_column_compression('cmaltertest', 'at1');
+ALTER TABLE cmaltertest ALTER COLUMN at1 SET DATA TYPE INT USING at1::INTEGER;
+\d+ cmaltertest
+SELECT pg_column_compression('cmaltertest', 'at1');
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass;
+ALTER TABLE cmaltertest ALTER COLUMN at1 SET DATA TYPE TEXT;
+SELECT pg_column_compression('cmaltertest', 'at1');
+DROP TABLE cmaltertest;
+
+-- test storages
+CREATE TABLE cmstoragetest(st1 TEXT, st2 INT);
+ALTER TABLE cmstoragetest ALTER COLUMN st2
+	SET COMPRESSION pglz WITH (min_input_size '100'); -- fail
+ALTER TABLE cmstoragetest ALTER COLUMN st1
+	SET COMPRESSION pglz WITH (min_input_size '100', min_comp_rate '50');
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE EXTERNAL;
+\d+ cmstoragetest
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE MAIN;
+\d+ cmstoragetest
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE PLAIN;
+\d+ cmstoragetest
+ALTER TABLE cmstoragetest ALTER COLUMN st1 SET STORAGE EXTENDED;
+\d+ cmstoragetest
+DROP TABLE cmstoragetest;
+
+CREATE ACCESS METHOD pglz2 TYPE COMPRESSION HANDLER pglzhandler;
+
+-- test PRESERVE
+CREATE TABLE cmtest(f1 TEXT);
+\d+ cmtest
+-- view to check dependencies
+CREATE VIEW cmtest_deps AS
+	SELECT classid, objsubid, refclassid, refobjsubid, deptype
+	FROM pg_depend
+	WHERE (refclassid = 4001 OR classid = 4001) AND
+		  (objid = 'cmtest'::regclass OR refobjid = 'cmtest'::regclass)
+	ORDER by objid, refobjid;
+INSERT INTO cmtest VALUES(repeat('1234567890',1001));
+
+-- one normal dependency
+SELECT * FROM cmtest_deps;
+
+-- check decompression
+SELECT length(f1) FROM cmtest;
+SELECT length(f1) FROM cmtest;
+
+CREATE FUNCTION on_cmtest_rewrite()
+RETURNS event_trigger AS $$
+BEGIN
+	RAISE NOTICE 'cmtest rewrite';
+END;
+$$  LANGUAGE plpgsql;
+
+CREATE EVENT TRIGGER notice_on_cmtest_rewrite ON table_rewrite
+	EXECUTE PROCEDURE on_cmtest_rewrite();
+
+-- no rewrite
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz1 PRESERVE (pglz);
+INSERT INTO cmtest VALUES(repeat('1234567890',1002));
+SELECT length(f1) FROM cmtest;
+SELECT pg_column_compression('cmtest', 'f1');
+-- one normal and one internal dependency
+SELECT * FROM cmtest_deps;
+
+-- rewrite
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz2 PRESERVE (pglz1);
+INSERT INTO cmtest VALUES(repeat('1234567890',1003));
+SELECT length(f1) FROM cmtest;
+SELECT pg_column_compression('cmtest', 'f1');
+-- two internal dependencies
+SELECT * FROM cmtest_deps;
+
+-- rewrite
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz;
+INSERT INTO cmtest VALUES(repeat('1234567890',1004));
+SELECT length(f1) FROM cmtest;
+SELECT pg_column_compression('cmtest', 'f1');
+-- one nornal dependency
+SELECT * FROM cmtest_deps;
+
+-- no rewrites
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz1 PRESERVE (pglz);
+INSERT INTO cmtest VALUES(repeat('1234567890',1005));
+ALTER TABLE cmtest ALTER COLUMN f1 SET COMPRESSION pglz
+	WITH (min_input_size '1000') PRESERVE (pglz, pglz1);
+INSERT INTO cmtest VALUES(repeat('1234567890',1006));
+-- one nornal dependency and two internal dependencies
+SELECT * FROM cmtest_deps;
+
+-- remove function and related event trigger
+DROP FUNCTION on_cmtest_rewrite CASCADE;
+
+-- test moving
+CREATE TABLE cmdata(f1 text COMPRESSION pglz WITH (first_success_by '10000'));
+INSERT INTO cmdata VALUES(repeat('1234567890',1000));
+INSERT INTO cmdata VALUES(repeat('1234567890',1001));
+
+-- copy with table creation
+SELECT * INTO cmmove1 FROM cmdata;
+
+-- we update using datum from different table
+CREATE TABLE cmmove2(f1 text COMPRESSION pglz WITH (min_input_size '100'));
+INSERT INTO cmmove2 VALUES (repeat('1234567890',1004));
+UPDATE cmmove2 SET f1 = cmdata.f1 FROM cmdata;
+
+-- copy to existing table
+CREATE TABLE cmmove3(f1 text COMPRESSION pglz WITH (min_input_size '100'));
+INSERT INTO cmmove3 SELECT * FROM cmdata;
+
+-- drop original compression information
+DROP TABLE cmdata;
+
+-- check data is ok
+SELECT length(f1) FROM cmmove1;
+SELECT length(f1) FROM cmmove2;
+SELECT length(f1) FROM cmmove3;
+
+-- create different types of tables
+CREATE TABLE cmexample (f1 TEXT COMPRESSION pglz1);
+CREATE TABLE cmtestlike1 (LIKE cmexample INCLUDING COMPRESSION);
+CREATE TABLE cmtestlike2 (f2 INT) INHERITS (cmexample);
+
+\d+ cmtestlike1
+\d+ cmtestlike2
+
+-- test two columns
+CREATE TABLE cmaltertest(f1 TEXT, f2 TEXT COMPRESSION pglz1);
+\d+ cmaltertest;
+-- fail, changing one column twice
+ALTER TABLE cmaltertest ALTER COLUMN f1 SET COMPRESSION pglz,
+	ALTER COLUMN f1 SET COMPRESSION pglz;
+
+-- with rewrite
+ALTER TABLE cmaltertest ALTER COLUMN f1 SET COMPRESSION pglz1,
+	ALTER COLUMN f2 SET COMPRESSION pglz PRESERVE (pglz1);
+SELECT pg_column_compression('cmaltertest', 'f1');
+SELECT pg_column_compression('cmaltertest', 'f2');
+
+-- no rewrite
+ALTER TABLE cmaltertest ALTER COLUMN f1 SET COMPRESSION pglz PRESERVE (pglz1),
+	ALTER COLUMN f2 SET COMPRESSION pglz2 PRESERVE (pglz1, pglz);
+SELECT pg_column_compression('cmaltertest', 'f1');
+SELECT pg_column_compression('cmaltertest', 'f2');
+
+-- make pglz2 droppable
+ALTER TABLE cmaltertest ALTER COLUMN f2 SET COMPRESSION pglz1;
+SELECT pg_column_compression('cmaltertest', 'f1');
+SELECT pg_column_compression('cmaltertest', 'f2');
+
+SELECT acname, acattnum, acoptions FROM pg_attr_compression
+	WHERE acrelid = 'cmaltertest'::regclass OR acrelid = 'cmtest'::regclass;
+
+-- zlib compression
+CREATE TABLE zlibtest(f1 TEXT COMPRESSION zlib WITH (invalid 'param'));
+CREATE TABLE zlibtest(f1 TEXT COMPRESSION zlib WITH (level 'best'));
+CREATE TABLE zlibtest(f1 TEXT COMPRESSION zlib);
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (level 'best_compression');
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (level 'best_speed');
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (level 'default');
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (dict 'one');
+INSERT INTO zlibtest VALUES(repeat('1234567890',1004));
+ALTER TABLE zlibtest
+	ALTER COLUMN f1 SET COMPRESSION zlib WITH (dict 'one two') PRESERVE (zlib);
+INSERT INTO zlibtest VALUES(repeat('1234567890 one two three',1004));
+SELECT length(f1) FROM zlibtest;
+
+DROP ACCESS METHOD pglz2;
+DROP VIEW cmtest_deps;
+DROP TABLE cmmove1, cmmove2, cmmove3;
+DROP TABLE cmexample, cmtestlike1, cmtestlike2, zlibtest;
diff --git a/src/test/regress/sql/opr_sanity.sql b/src/test/regress/sql/opr_sanity.sql
index e2014fc2b5..64bbe004b0 100644
--- a/src/test/regress/sql/opr_sanity.sql
+++ b/src/test/regress/sql/opr_sanity.sql
@@ -1206,7 +1206,9 @@ WHERE p1.amhandler = 0;
 SELECT p1.oid, p1.amname, p2.oid, p2.proname
 FROM pg_am AS p1, pg_proc AS p2
 WHERE p2.oid = p1.amhandler AND
-    (p2.prorettype != 'index_am_handler'::regtype OR p2.proretset
+    ((p2.prorettype != 'index_am_handler'::regtype
+      AND p2.prorettype != 'compression_am_handler'::regtype)
+     OR p2.proretset
      OR p2.pronargs != 1
      OR p2.proargtypes[0] != 'internal'::regtype);
 
-- 
2.20.1


--MP_/IoARF=uydinlHTRfc3S.uiA
Content-Type: text/x-patch
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename=0008-Add-documentation-for-custom-compression-methods-v21.patch



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH 2/3] Add operator <->(box, point) to GiST box_ops
@ 2019-03-07 20:49 Nikita Glukhov <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Nikita Glukhov @ 2019-03-07 20:49 UTC (permalink / raw)

---
 doc/src/sgml/gist.sgml             |  1 +
 src/backend/access/gist/gistproc.c | 53 ++++++++++++++++++--------
 src/include/catalog/pg_amop.dat    |  3 ++
 src/include/catalog/pg_amproc.dat  |  2 +
 src/include/catalog/pg_proc.dat    |  4 ++
 src/test/regress/expected/gist.out | 76 ++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gist.sql      | 16 ++++++++
 7 files changed, 140 insertions(+), 15 deletions(-)

diff --git a/doc/src/sgml/gist.sgml b/doc/src/sgml/gist.sgml
index 44a3b2c..e903319 100644
--- a/doc/src/sgml/gist.sgml
+++ b/doc/src/sgml/gist.sgml
@@ -83,6 +83,7 @@
        <literal>~=</literal>
       </entry>
       <entry>
+       <literal>&lt;-&gt;</literal>
       </entry>
      </row>
      <row>
diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c
index c9da230..89643b4 100644
--- a/src/backend/access/gist/gistproc.c
+++ b/src/backend/access/gist/gistproc.c
@@ -1464,26 +1464,13 @@ gist_point_distance(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(distance);
 }
 
-/*
- * The inexact GiST distance method for geometric types that store bounding
- * boxes.
- *
- * Compute lossy distance from point to index entries.  The result is inexact
- * because index entries are bounding boxes, not the exact shapes of the
- * indexed geometric types.  We use distance from point to MBR of index entry.
- * This is a lower bound estimate of distance from point to indexed geometric
- * type.
- */
 static float8
-gist_bbox_distance(GISTENTRY *entry, Datum query,
-				   StrategyNumber strategy, bool *recheck)
+gist_box_distance_helper(GISTENTRY *entry, Datum query,
+						 StrategyNumber strategy)
 {
 	float8		distance;
 	StrategyNumber strategyGroup = strategy / GeoStrategyNumberOffset;
 
-	/* Bounding box distance is always inexact. */
-	*recheck = true;
-
 	switch (strategyGroup)
 	{
 		case PointStrategyNumberGroup:
@@ -1500,6 +1487,42 @@ gist_bbox_distance(GISTENTRY *entry, Datum query,
 }
 
 Datum
+gist_box_distance(PG_FUNCTION_ARGS)
+{
+	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+	Datum		query = PG_GETARG_DATUM(1);
+	StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
+
+	/* Oid subtype = PG_GETARG_OID(3); */
+	/* bool	   *recheck = (bool *) PG_GETARG_POINTER(4); */
+	float8		distance;
+
+	distance = gist_box_distance_helper(entry, query, strategy);
+
+	PG_RETURN_FLOAT8(distance);
+}
+
+/*
+ * The inexact GiST distance method for geometric types that store bounding
+ * boxes.
+ *
+ * Compute lossy distance from point to index entries.  The result is inexact
+ * because index entries are bounding boxes, not the exact shapes of the
+ * indexed geometric types.  We use distance from point to MBR of index entry.
+ * This is a lower bound estimate of distance from point to indexed geometric
+ * type.
+ */
+static float8
+gist_bbox_distance(GISTENTRY *entry, Datum query,
+				   StrategyNumber strategy, bool *recheck)
+{
+	/* Bounding box distance is always inexact. */
+	*recheck = true;
+
+	return gist_box_distance_helper(entry, query, strategy);
+}
+
+Datum
 gist_circle_distance(PG_FUNCTION_ARGS)
 {
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index 0ab95d8..8e74f86 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1081,6 +1081,9 @@
   amopstrategy => '13', amopopr => '~(box,box)', amopmethod => 'gist' },
 { amopfamily => 'gist/box_ops', amoplefttype => 'box', amoprighttype => 'box',
   amopstrategy => '14', amopopr => '@(box,box)', amopmethod => 'gist' },
+{ amopfamily => 'gist/box_ops', amoplefttype => 'box', amoprighttype => 'point',
+  amopstrategy => '15', amoppurpose => 'o', amopopr => '<->(box,point)',
+  amopmethod => 'gist', amopsortfamily => 'btree/float_ops' },
 
 # gist point_ops
 { amopfamily => 'gist/point_ops', amoplefttype => 'point',
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 020b741..5567b7e 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -419,6 +419,8 @@
   amprocrighttype => 'box', amprocnum => '6', amproc => 'gist_box_picksplit' },
 { amprocfamily => 'gist/box_ops', amproclefttype => 'box',
   amprocrighttype => 'box', amprocnum => '7', amproc => 'gist_box_same' },
+{ amprocfamily => 'gist/box_ops', amproclefttype => 'box',
+  amprocrighttype => 'box', amprocnum => '8', amproc => 'gist_box_distance' },
 { amprocfamily => 'gist/poly_ops', amproclefttype => 'polygon',
   amprocrighttype => 'polygon', amprocnum => '1',
   amproc => 'gist_poly_consistent' },
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3800150..d6477a3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7760,6 +7760,10 @@
 { oid => '2584', descr => 'GiST support',
   proname => 'gist_box_same', prorettype => 'internal',
   proargtypes => 'box box internal', prosrc => 'gist_box_same' },
+{ oid => '3998', descr => 'GiST support',
+  proname => 'gist_box_distance', prorettype => 'float8',
+  proargtypes => 'internal box int2 oid internal',
+  prosrc => 'gist_box_distance' },
 { oid => '2585', descr => 'GiST support',
   proname => 'gist_poly_consistent', prorettype => 'bool',
   proargtypes => 'internal polygon int2 oid internal',
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index f5a2993..e8c2685 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -205,6 +205,82 @@ select b from gist_tbl where b <@ box(point(5,5), point(6,6));
  (6,6),(6,6)
 (21 rows)
 
+-- Also test an index-only knn-search
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Index Only Scan using gist_tbl_box_index on gist_tbl
+   Index Cond: (b <@ '(6,6),(5,5)'::box)
+   Order By: (b <-> '(5.2,5.91)'::point)
+(3 rows)
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+            b            
+-------------------------
+ (5.55,5.55),(5.55,5.55)
+ (5.6,5.6),(5.6,5.6)
+ (5.5,5.5),(5.5,5.5)
+ (5.65,5.65),(5.65,5.65)
+ (5.45,5.45),(5.45,5.45)
+ (5.7,5.7),(5.7,5.7)
+ (5.4,5.4),(5.4,5.4)
+ (5.75,5.75),(5.75,5.75)
+ (5.35,5.35),(5.35,5.35)
+ (5.8,5.8),(5.8,5.8)
+ (5.3,5.3),(5.3,5.3)
+ (5.85,5.85),(5.85,5.85)
+ (5.25,5.25),(5.25,5.25)
+ (5.9,5.9),(5.9,5.9)
+ (5.2,5.2),(5.2,5.2)
+ (5.95,5.95),(5.95,5.95)
+ (5.15,5.15),(5.15,5.15)
+ (6,6),(6,6)
+ (5.1,5.1),(5.1,5.1)
+ (5.05,5.05),(5.05,5.05)
+ (5,5),(5,5)
+(21 rows)
+
+-- Check commuted case as well
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Index Only Scan using gist_tbl_box_index on gist_tbl
+   Index Cond: (b <@ '(6,6),(5,5)'::box)
+   Order By: (b <-> '(5.2,5.91)'::point)
+(3 rows)
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+            b            
+-------------------------
+ (5.55,5.55),(5.55,5.55)
+ (5.6,5.6),(5.6,5.6)
+ (5.5,5.5),(5.5,5.5)
+ (5.65,5.65),(5.65,5.65)
+ (5.45,5.45),(5.45,5.45)
+ (5.7,5.7),(5.7,5.7)
+ (5.4,5.4),(5.4,5.4)
+ (5.75,5.75),(5.75,5.75)
+ (5.35,5.35),(5.35,5.35)
+ (5.8,5.8),(5.8,5.8)
+ (5.3,5.3),(5.3,5.3)
+ (5.85,5.85),(5.85,5.85)
+ (5.25,5.25),(5.25,5.25)
+ (5.9,5.9),(5.9,5.9)
+ (5.2,5.2),(5.2,5.2)
+ (5.95,5.95),(5.95,5.95)
+ (5.15,5.15),(5.15,5.15)
+ (6,6),(6,6)
+ (5.1,5.1),(5.1,5.1)
+ (5.05,5.05),(5.05,5.05)
+ (5,5),(5,5)
+(21 rows)
+
 drop index gist_tbl_box_index;
 -- Test that an index-only scan is not chosen, when the query involves the
 -- circle column (the circle opclass does not support index-only scans).
diff --git a/src/test/regress/sql/gist.sql b/src/test/regress/sql/gist.sql
index bae722f..406fb83 100644
--- a/src/test/regress/sql/gist.sql
+++ b/src/test/regress/sql/gist.sql
@@ -111,6 +111,22 @@ select b from gist_tbl where b <@ box(point(5,5), point(6,6));
 -- execute the same
 select b from gist_tbl where b <@ box(point(5,5), point(6,6));
 
+-- Also test an index-only knn-search
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+
+-- Check commuted case as well
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+
 drop index gist_tbl_box_index;
 
 -- Test that an index-only scan is not chosen, when the query involves the
-- 
2.7.4


--------------B01ADF66983FA504E371CA13
Content-Type: text/x-patch;
 name="0003-Add-box-point-distance-operator-to-SP-GiST-box_ops-v01.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename*0="0003-Add-box-point-distance-operator-to-SP-GiST-box_ops-v01.";
 filename*1="patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH 2/3] Add operator <->(box, point) to GiST box_ops
@ 2019-03-07 20:49 Nikita Glukhov <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Nikita Glukhov @ 2019-03-07 20:49 UTC (permalink / raw)

---
 doc/src/sgml/gist.sgml             |  1 +
 src/backend/access/gist/gistproc.c | 53 ++++++++++++++++++--------
 src/include/catalog/pg_amop.dat    |  3 ++
 src/include/catalog/pg_amproc.dat  |  2 +
 src/include/catalog/pg_proc.dat    |  4 ++
 src/test/regress/expected/gist.out | 76 ++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gist.sql      | 16 ++++++++
 7 files changed, 140 insertions(+), 15 deletions(-)

diff --git a/doc/src/sgml/gist.sgml b/doc/src/sgml/gist.sgml
index 44a3b2c..e903319 100644
--- a/doc/src/sgml/gist.sgml
+++ b/doc/src/sgml/gist.sgml
@@ -83,6 +83,7 @@
        <literal>~=</literal>
       </entry>
       <entry>
+       <literal>&lt;-&gt;</literal>
       </entry>
      </row>
      <row>
diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c
index 1826b51..89bd656 100644
--- a/src/backend/access/gist/gistproc.c
+++ b/src/backend/access/gist/gistproc.c
@@ -1464,26 +1464,13 @@ gist_point_distance(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(distance);
 }
 
-/*
- * The inexact GiST distance method for geometric types that store bounding
- * boxes.
- *
- * Compute lossy distance from point to index entries.  The result is inexact
- * because index entries are bounding boxes, not the exact shapes of the
- * indexed geometric types.  We use distance from point to MBR of index entry.
- * This is a lower bound estimate of distance from point to indexed geometric
- * type.
- */
 static float8
-gist_bbox_distance(GISTENTRY *entry, Datum query,
-				   StrategyNumber strategy, bool *recheck)
+gist_box_distance_helper(GISTENTRY *entry, Datum query,
+						 StrategyNumber strategy)
 {
 	float8		distance;
 	StrategyNumber strategyGroup = strategy / GeoStrategyNumberOffset;
 
-	/* Bounding box distance is always inexact. */
-	*recheck = true;
-
 	switch (strategyGroup)
 	{
 		case PointStrategyNumberGroup:
@@ -1500,6 +1487,42 @@ gist_bbox_distance(GISTENTRY *entry, Datum query,
 }
 
 Datum
+gist_box_distance(PG_FUNCTION_ARGS)
+{
+	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+	Datum		query = PG_GETARG_DATUM(1);
+	StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
+
+	/* Oid subtype = PG_GETARG_OID(3); */
+	/* bool	   *recheck = (bool *) PG_GETARG_POINTER(4); */
+	float8		distance;
+
+	distance = gist_box_distance_helper(entry, query, strategy);
+
+	PG_RETURN_FLOAT8(distance);
+}
+
+/*
+ * The inexact GiST distance method for geometric types that store bounding
+ * boxes.
+ *
+ * Compute lossy distance from point to index entries.  The result is inexact
+ * because index entries are bounding boxes, not the exact shapes of the
+ * indexed geometric types.  We use distance from point to MBR of index entry.
+ * This is a lower bound estimate of distance from point to indexed geometric
+ * type.
+ */
+static float8
+gist_bbox_distance(GISTENTRY *entry, Datum query,
+				   StrategyNumber strategy, bool *recheck)
+{
+	/* Bounding box distance is always inexact. */
+	*recheck = true;
+
+	return gist_box_distance_helper(entry, query, strategy);
+}
+
+Datum
 gist_circle_distance(PG_FUNCTION_ARGS)
 {
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index cf63eb7..ebc38ae 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1081,6 +1081,9 @@
   amopstrategy => '13', amopopr => '~(box,box)', amopmethod => 'gist' },
 { amopfamily => 'gist/box_ops', amoplefttype => 'box', amoprighttype => 'box',
   amopstrategy => '14', amopopr => '@(box,box)', amopmethod => 'gist' },
+{ amopfamily => 'gist/box_ops', amoplefttype => 'box', amoprighttype => 'point',
+  amopstrategy => '15', amoppurpose => 'o', amopopr => '<->(box,point)',
+  amopmethod => 'gist', amopsortfamily => 'btree/float_ops' },
 
 # gist point_ops
 { amopfamily => 'gist/point_ops', amoplefttype => 'point',
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 020b741..5567b7e 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -419,6 +419,8 @@
   amprocrighttype => 'box', amprocnum => '6', amproc => 'gist_box_picksplit' },
 { amprocfamily => 'gist/box_ops', amproclefttype => 'box',
   amprocrighttype => 'box', amprocnum => '7', amproc => 'gist_box_same' },
+{ amprocfamily => 'gist/box_ops', amproclefttype => 'box',
+  amprocrighttype => 'box', amprocnum => '8', amproc => 'gist_box_distance' },
 { amprocfamily => 'gist/poly_ops', amproclefttype => 'polygon',
   amprocrighttype => 'polygon', amprocnum => '1',
   amproc => 'gist_poly_consistent' },
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 54327bf..d15f94a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7816,6 +7816,10 @@
 { oid => '2584', descr => 'GiST support',
   proname => 'gist_box_same', prorettype => 'internal',
   proargtypes => 'box box internal', prosrc => 'gist_box_same' },
+{ oid => '3998', descr => 'GiST support',
+  proname => 'gist_box_distance', prorettype => 'float8',
+  proargtypes => 'internal box int2 oid internal',
+  prosrc => 'gist_box_distance' },
 { oid => '2585', descr => 'GiST support',
   proname => 'gist_poly_consistent', prorettype => 'bool',
   proargtypes => 'internal polygon int2 oid internal',
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index 0a43449..2234876 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -203,6 +203,82 @@ select b from gist_tbl where b <@ box(point(5,5), point(6,6));
  (6,6),(6,6)
 (21 rows)
 
+-- Also test an index-only knn-search
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Index Only Scan using gist_tbl_box_index on gist_tbl
+   Index Cond: (b <@ '(6,6),(5,5)'::box)
+   Order By: (b <-> '(5.2,5.91)'::point)
+(3 rows)
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+            b            
+-------------------------
+ (5.55,5.55),(5.55,5.55)
+ (5.6,5.6),(5.6,5.6)
+ (5.5,5.5),(5.5,5.5)
+ (5.65,5.65),(5.65,5.65)
+ (5.45,5.45),(5.45,5.45)
+ (5.7,5.7),(5.7,5.7)
+ (5.4,5.4),(5.4,5.4)
+ (5.75,5.75),(5.75,5.75)
+ (5.35,5.35),(5.35,5.35)
+ (5.8,5.8),(5.8,5.8)
+ (5.3,5.3),(5.3,5.3)
+ (5.85,5.85),(5.85,5.85)
+ (5.25,5.25),(5.25,5.25)
+ (5.9,5.9),(5.9,5.9)
+ (5.2,5.2),(5.2,5.2)
+ (5.95,5.95),(5.95,5.95)
+ (5.15,5.15),(5.15,5.15)
+ (6,6),(6,6)
+ (5.1,5.1),(5.1,5.1)
+ (5.05,5.05),(5.05,5.05)
+ (5,5),(5,5)
+(21 rows)
+
+-- Check commuted case as well
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Index Only Scan using gist_tbl_box_index on gist_tbl
+   Index Cond: (b <@ '(6,6),(5,5)'::box)
+   Order By: (b <-> '(5.2,5.91)'::point)
+(3 rows)
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+            b            
+-------------------------
+ (5.55,5.55),(5.55,5.55)
+ (5.6,5.6),(5.6,5.6)
+ (5.5,5.5),(5.5,5.5)
+ (5.65,5.65),(5.65,5.65)
+ (5.45,5.45),(5.45,5.45)
+ (5.7,5.7),(5.7,5.7)
+ (5.4,5.4),(5.4,5.4)
+ (5.75,5.75),(5.75,5.75)
+ (5.35,5.35),(5.35,5.35)
+ (5.8,5.8),(5.8,5.8)
+ (5.3,5.3),(5.3,5.3)
+ (5.85,5.85),(5.85,5.85)
+ (5.25,5.25),(5.25,5.25)
+ (5.9,5.9),(5.9,5.9)
+ (5.2,5.2),(5.2,5.2)
+ (5.95,5.95),(5.95,5.95)
+ (5.15,5.15),(5.15,5.15)
+ (6,6),(6,6)
+ (5.1,5.1),(5.1,5.1)
+ (5.05,5.05),(5.05,5.05)
+ (5,5),(5,5)
+(21 rows)
+
 drop index gist_tbl_box_index;
 -- Test that an index-only scan is not chosen, when the query involves the
 -- circle column (the circle opclass does not support index-only scans).
diff --git a/src/test/regress/sql/gist.sql b/src/test/regress/sql/gist.sql
index 657b195..b9d398e 100644
--- a/src/test/regress/sql/gist.sql
+++ b/src/test/regress/sql/gist.sql
@@ -109,6 +109,22 @@ select b from gist_tbl where b <@ box(point(5,5), point(6,6));
 -- execute the same
 select b from gist_tbl where b <@ box(point(5,5), point(6,6));
 
+-- Also test an index-only knn-search
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+
+-- Check commuted case as well
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+
 drop index gist_tbl_box_index;
 
 -- Test that an index-only scan is not chosen, when the query involves the
-- 
2.7.4


--------------07EE9595454874974EAD75E7
Content-Type: text/x-patch;
 name="0003-Add-operator-box-point-to-SP-GiST-box_ops-v02.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Add-operator-box-point-to-SP-GiST-box_ops-v02.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH 2/3] Add operator <->(box, point) to GiST box_ops
@ 2019-03-07 20:49 Nikita Glukhov <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Nikita Glukhov @ 2019-03-07 20:49 UTC (permalink / raw)

---
 doc/src/sgml/gist.sgml             |  1 +
 src/backend/access/gist/gistproc.c | 49 +++++++++++++++---------
 src/include/catalog/pg_amop.dat    |  3 ++
 src/include/catalog/pg_amproc.dat  |  2 +
 src/include/catalog/pg_proc.dat    |  4 ++
 src/test/regress/expected/gist.out | 76 ++++++++++++++++++++++++++++++++++++++
 src/test/regress/sql/gist.sql      | 16 ++++++++
 7 files changed, 134 insertions(+), 17 deletions(-)

diff --git a/doc/src/sgml/gist.sgml b/doc/src/sgml/gist.sgml
index 763b8cf..2b43dfc 100644
--- a/doc/src/sgml/gist.sgml
+++ b/doc/src/sgml/gist.sgml
@@ -83,6 +83,7 @@
        <literal>~=</literal>
       </entry>
       <entry>
+       <literal>&lt;-&gt;</literal>
       </entry>
      </row>
      <row>
diff --git a/src/backend/access/gist/gistproc.c b/src/backend/access/gist/gistproc.c
index 1826b51..a668283 100644
--- a/src/backend/access/gist/gistproc.c
+++ b/src/backend/access/gist/gistproc.c
@@ -1464,26 +1464,13 @@ gist_point_distance(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8(distance);
 }
 
-/*
- * The inexact GiST distance method for geometric types that store bounding
- * boxes.
- *
- * Compute lossy distance from point to index entries.  The result is inexact
- * because index entries are bounding boxes, not the exact shapes of the
- * indexed geometric types.  We use distance from point to MBR of index entry.
- * This is a lower bound estimate of distance from point to indexed geometric
- * type.
- */
 static float8
-gist_bbox_distance(GISTENTRY *entry, Datum query,
-				   StrategyNumber strategy, bool *recheck)
+gist_box_distance_helper(GISTENTRY *entry, Datum query,
+						 StrategyNumber strategy)
 {
 	float8		distance;
 	StrategyNumber strategyGroup = strategy / GeoStrategyNumberOffset;
 
-	/* Bounding box distance is always inexact. */
-	*recheck = true;
-
 	switch (strategyGroup)
 	{
 		case PointStrategyNumberGroup:
@@ -1500,6 +1487,32 @@ gist_bbox_distance(GISTENTRY *entry, Datum query,
 }
 
 Datum
+gist_box_distance(PG_FUNCTION_ARGS)
+{
+	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+	Datum		query = PG_GETARG_DATUM(1);
+	StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
+
+	/* Oid subtype = PG_GETARG_OID(3); */
+	/* bool	   *recheck = (bool *) PG_GETARG_POINTER(4); */
+	float8		distance;
+
+	distance = gist_box_distance_helper(entry, query, strategy);
+
+	PG_RETURN_FLOAT8(distance);
+}
+
+/*
+ * The inexact GiST distance methods for geometric types that store bounding
+ * boxes.
+ *
+ * Compute lossy distance from point to index entries.  The result is inexact
+ * because index entries are bounding boxes, not the exact shapes of the
+ * indexed geometric types.  We use distance from point to MBR of index entry.
+ * This is a lower bound estimate of distance from point to indexed geometric
+ * type.
+ */
+Datum
 gist_circle_distance(PG_FUNCTION_ARGS)
 {
 	GISTENTRY  *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
@@ -1510,7 +1523,8 @@ gist_circle_distance(PG_FUNCTION_ARGS)
 	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
 	float8		distance;
 
-	distance = gist_bbox_distance(entry, query, strategy, recheck);
+	distance = gist_box_distance_helper(entry, query, strategy);
+	*recheck = true;
 
 	PG_RETURN_FLOAT8(distance);
 }
@@ -1526,7 +1540,8 @@ gist_poly_distance(PG_FUNCTION_ARGS)
 	bool	   *recheck = (bool *) PG_GETARG_POINTER(4);
 	float8		distance;
 
-	distance = gist_bbox_distance(entry, query, strategy, recheck);
+	distance = gist_box_distance_helper(entry, query, strategy);
+	*recheck = true;
 
 	PG_RETURN_FLOAT8(distance);
 }
diff --git a/src/include/catalog/pg_amop.dat b/src/include/catalog/pg_amop.dat
index cf63eb7..ebc38ae 100644
--- a/src/include/catalog/pg_amop.dat
+++ b/src/include/catalog/pg_amop.dat
@@ -1081,6 +1081,9 @@
   amopstrategy => '13', amopopr => '~(box,box)', amopmethod => 'gist' },
 { amopfamily => 'gist/box_ops', amoplefttype => 'box', amoprighttype => 'box',
   amopstrategy => '14', amopopr => '@(box,box)', amopmethod => 'gist' },
+{ amopfamily => 'gist/box_ops', amoplefttype => 'box', amoprighttype => 'point',
+  amopstrategy => '15', amoppurpose => 'o', amopopr => '<->(box,point)',
+  amopmethod => 'gist', amopsortfamily => 'btree/float_ops' },
 
 # gist point_ops
 { amopfamily => 'gist/point_ops', amoplefttype => 'point',
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 020b741..5567b7e 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -419,6 +419,8 @@
   amprocrighttype => 'box', amprocnum => '6', amproc => 'gist_box_picksplit' },
 { amprocfamily => 'gist/box_ops', amproclefttype => 'box',
   amprocrighttype => 'box', amprocnum => '7', amproc => 'gist_box_same' },
+{ amprocfamily => 'gist/box_ops', amproclefttype => 'box',
+  amprocrighttype => 'box', amprocnum => '8', amproc => 'gist_box_distance' },
 { amprocfamily => 'gist/poly_ops', amproclefttype => 'polygon',
   amprocrighttype => 'polygon', amprocnum => '1',
   amproc => 'gist_poly_consistent' },
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e0852b8..762c099 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7844,6 +7844,10 @@
 { oid => '2584', descr => 'GiST support',
   proname => 'gist_box_same', prorettype => 'internal',
   proargtypes => 'box box internal', prosrc => 'gist_box_same' },
+{ oid => '3998', descr => 'GiST support',
+  proname => 'gist_box_distance', prorettype => 'float8',
+  proargtypes => 'internal box int2 oid internal',
+  prosrc => 'gist_box_distance' },
 { oid => '2585', descr => 'GiST support',
   proname => 'gist_poly_consistent', prorettype => 'bool',
   proargtypes => 'internal polygon int2 oid internal',
diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out
index 0a43449..2234876 100644
--- a/src/test/regress/expected/gist.out
+++ b/src/test/regress/expected/gist.out
@@ -203,6 +203,82 @@ select b from gist_tbl where b <@ box(point(5,5), point(6,6));
  (6,6),(6,6)
 (21 rows)
 
+-- Also test an index-only knn-search
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Index Only Scan using gist_tbl_box_index on gist_tbl
+   Index Cond: (b <@ '(6,6),(5,5)'::box)
+   Order By: (b <-> '(5.2,5.91)'::point)
+(3 rows)
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+            b            
+-------------------------
+ (5.55,5.55),(5.55,5.55)
+ (5.6,5.6),(5.6,5.6)
+ (5.5,5.5),(5.5,5.5)
+ (5.65,5.65),(5.65,5.65)
+ (5.45,5.45),(5.45,5.45)
+ (5.7,5.7),(5.7,5.7)
+ (5.4,5.4),(5.4,5.4)
+ (5.75,5.75),(5.75,5.75)
+ (5.35,5.35),(5.35,5.35)
+ (5.8,5.8),(5.8,5.8)
+ (5.3,5.3),(5.3,5.3)
+ (5.85,5.85),(5.85,5.85)
+ (5.25,5.25),(5.25,5.25)
+ (5.9,5.9),(5.9,5.9)
+ (5.2,5.2),(5.2,5.2)
+ (5.95,5.95),(5.95,5.95)
+ (5.15,5.15),(5.15,5.15)
+ (6,6),(6,6)
+ (5.1,5.1),(5.1,5.1)
+ (5.05,5.05),(5.05,5.05)
+ (5,5),(5,5)
+(21 rows)
+
+-- Check commuted case as well
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+                      QUERY PLAN                      
+------------------------------------------------------
+ Index Only Scan using gist_tbl_box_index on gist_tbl
+   Index Cond: (b <@ '(6,6),(5,5)'::box)
+   Order By: (b <-> '(5.2,5.91)'::point)
+(3 rows)
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+            b            
+-------------------------
+ (5.55,5.55),(5.55,5.55)
+ (5.6,5.6),(5.6,5.6)
+ (5.5,5.5),(5.5,5.5)
+ (5.65,5.65),(5.65,5.65)
+ (5.45,5.45),(5.45,5.45)
+ (5.7,5.7),(5.7,5.7)
+ (5.4,5.4),(5.4,5.4)
+ (5.75,5.75),(5.75,5.75)
+ (5.35,5.35),(5.35,5.35)
+ (5.8,5.8),(5.8,5.8)
+ (5.3,5.3),(5.3,5.3)
+ (5.85,5.85),(5.85,5.85)
+ (5.25,5.25),(5.25,5.25)
+ (5.9,5.9),(5.9,5.9)
+ (5.2,5.2),(5.2,5.2)
+ (5.95,5.95),(5.95,5.95)
+ (5.15,5.15),(5.15,5.15)
+ (6,6),(6,6)
+ (5.1,5.1),(5.1,5.1)
+ (5.05,5.05),(5.05,5.05)
+ (5,5),(5,5)
+(21 rows)
+
 drop index gist_tbl_box_index;
 -- Test that an index-only scan is not chosen, when the query involves the
 -- circle column (the circle opclass does not support index-only scans).
diff --git a/src/test/regress/sql/gist.sql b/src/test/regress/sql/gist.sql
index 657b195..b9d398e 100644
--- a/src/test/regress/sql/gist.sql
+++ b/src/test/regress/sql/gist.sql
@@ -109,6 +109,22 @@ select b from gist_tbl where b <@ box(point(5,5), point(6,6));
 -- execute the same
 select b from gist_tbl where b <@ box(point(5,5), point(6,6));
 
+-- Also test an index-only knn-search
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by b <-> point(5.2, 5.91);
+
+-- Check commuted case as well
+explain (costs off)
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+
+select b from gist_tbl where b <@ box(point(5,5), point(6,6))
+order by point(5.2, 5.91) <-> b;
+
 drop index gist_tbl_box_index;
 
 -- Test that an index-only scan is not chosen, when the query involves the
-- 
2.7.4


--------------C466D744B1EAB9FA85D22553
Content-Type: text/x-patch;
 name="0003-Add-operator-box-point-to-SP-GiST-box_ops-v03.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="0003-Add-operator-box-point-to-SP-GiST-box_ops-v03.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v1 4/7] Row pattern recognition patch (executor).
@ 2023-06-25 11:48 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-06-25 11:48 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 223 +++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  | 274 ++++++++++++++++++++++++++-
 src/include/catalog/pg_proc.dat      |   9 +
 src/include/nodes/execnodes.h        |  12 ++
 src/include/windowapi.h              |   9 +
 5 files changed, 517 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..ae997dff86 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -48,6 +48,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2692,69 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+			if (func->winfnoid != F_RPR)
+				continue;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "RPR's arg is not Var");
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2762,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2849,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2900,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3242,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3420,14 +3582,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3583,15 +3784,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3821,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..63b225271c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -39,7 +42,9 @@ typedef struct
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
-
+static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos);
+static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+							int relpos, char *vname, char *quantifier, bool *result);
 
 /*
  * utility routine for *_rank functions.
@@ -713,3 +718,270 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * rpr
+ * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in
+ * the target list.
+ * Usage: SELECT rpr(colname) OVER (..)
+ * where colname is defined in PATTERN clause.
+ */
+Datum
+window_rpr(PG_FUNCTION_ARGS)
+{
+#define	MAX_PATTERNS	16	/* max variables in PATTERN clause */
+
+	WindowObject winobj = PG_WINDOW_OBJECT();
+	WindowAggState	*winstate = WinGetAggState(winobj);
+	Datum		result;
+	bool		expression_result;
+	bool		isnull;
+	int			relpos;
+	int64		curr_pos, markpos;
+	ListCell	*lc, *lc1;
+
+	curr_pos = WinGetCurrentPosition(winobj);
+	elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos);
+
+	/*
+	 * Evaluate PATTERN until one of expressions is not true or out of frame.
+	 */
+	relpos = 0;
+
+	forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc));
+		char	*quantifier = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier);
+
+		/* evaluate row pattern against current row */
+		relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result);
+
+		/*
+		 * If the expression did not match, we are done.
+		 */
+		if (!expression_result)
+			break;
+
+		/* out of frame? */
+		if (relpos < 0)
+			break;
+
+		/* count up relative row position */
+		relpos++;
+	}
+
+	elog(DEBUG1, "relpos: %d", relpos);
+
+	/*
+	 * If current row satified the pattern, return argument expression.
+	 */
+	if (expression_result)
+	{
+		result = WinGetFuncArgInFrame(winobj, 0,
+									  0, WINDOW_SEEK_HEAD, false,
+									  &isnull, NULL);
+	}
+
+	/*
+	 * At this point we can set mark down to current pos -2.
+	 */
+	markpos = curr_pos -2;
+	elog(DEBUG1, "markpos: " INT64_FORMAT, markpos);
+	if (markpos >= 0)
+		WinSetMarkPosition(winobj, markpos);
+
+	if (expression_result)
+		PG_RETURN_DATUM(result);
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match relative row position
+ * -1: current row is out of frame
+ */
+static
+int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+					 int relpos, char *vname, char *quantifier, bool *result)
+{
+	ExprContext	*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell	*lc1, *lc2;
+	ExprState	*pat;
+	Datum		eval_result;
+	int			sts;
+	bool		out_of_frame = false;
+	bool		isnull;
+	StringInfo	encoded_str = makeStringInfo();
+	char		pattern_str[128];
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+		bool	second_try_match = false;
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		for (;;)
+		{
+			if (!get_slots(winobj, winstate, relpos))
+			{
+				out_of_frame = true;
+				break;	/* current row is out of frame */
+			}
+
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos);
+				break;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos);
+					break;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos);
+					appendStringInfoChar(encoded_str, vname[0]);
+
+					/* If quantifier is "+", we need to look for more matching row */
+					if (quantifier && !strcmp(quantifier, "+"))
+					{
+						/* remember that we want to try another row */
+						second_try_match = true;
+						relpos++;
+					}
+					else
+						break;
+				}
+			}
+		}
+		if (second_try_match)
+			relpos--;
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+
+		/* build regular expression */
+		snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier);
+
+		/*
+		 * Do regular expression matching against sequence of rows satisfying
+		 * the expression using regexp_instr().
+		 */
+		sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													PointerGetDatum(cstring_to_text(encoded_str->data)),
+													PointerGetDatum(cstring_to_text(pattern_str))));
+		elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s",
+			 sts, encoded_str->data, pattern_str);
+		*result = (sts > 0)? true : false;
+	}
+	return relpos;
+}
+
+/*
+ * Get current, previous and next tuple.
+ * Returns true if still within frame.
+ */
+static bool
+get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos)
+{
+	TupleTableSlot *slot;
+	bool	isnull, isout;
+	int		sts;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* for current row */
+	slot = winstate->temp_slot_1;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "current row is out of frame");
+		econtext->ecxt_scantuple = winstate->null_slot;
+		return false;
+	}
+	else
+		econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		sts = WinGetSlotInFrame(winobj, slot,
+								current_pos - 1, WINDOW_SEEK_HEAD, false,
+								&isnull, &isout);
+		if (sts < 0)
+		{
+			elog(DEBUG1, "previous row out of frame at: %d", current_pos);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		econtext->ecxt_outertuple = slot;
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos + 1, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "next row out of frame at: %d", current_pos);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+		econtext->ecxt_innertuple = slot;
+
+	return true;
+}
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..e3a9e0ffeb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,15 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'row pattern recognition in window',
+  proname => 'rpr', prokind => 'w', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_rpr' },
+{ oid => '6123', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6124', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..bc95c5fe9b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,13 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('*', '+' or '?'. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2562,11 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..a0facf38fe 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Sun_Jun_25_21_05_09_2023_126)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v1 4/7] Row pattern recognition patch (executor).
@ 2023-06-25 11:48 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-06-25 11:48 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 223 +++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  | 274 ++++++++++++++++++++++++++-
 src/include/catalog/pg_proc.dat      |   9 +
 src/include/nodes/execnodes.h        |  12 ++
 src/include/windowapi.h              |   9 +
 5 files changed, 517 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..ae997dff86 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -48,6 +48,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2692,69 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+			if (func->winfnoid != F_RPR)
+				continue;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "RPR's arg is not Var");
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2762,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2849,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2900,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3242,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3420,14 +3582,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3583,15 +3784,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3821,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..63b225271c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -39,7 +42,9 @@ typedef struct
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
-
+static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos);
+static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+							int relpos, char *vname, char *quantifier, bool *result);
 
 /*
  * utility routine for *_rank functions.
@@ -713,3 +718,270 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * rpr
+ * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in
+ * the target list.
+ * Usage: SELECT rpr(colname) OVER (..)
+ * where colname is defined in PATTERN clause.
+ */
+Datum
+window_rpr(PG_FUNCTION_ARGS)
+{
+#define	MAX_PATTERNS	16	/* max variables in PATTERN clause */
+
+	WindowObject winobj = PG_WINDOW_OBJECT();
+	WindowAggState	*winstate = WinGetAggState(winobj);
+	Datum		result;
+	bool		expression_result;
+	bool		isnull;
+	int			relpos;
+	int64		curr_pos, markpos;
+	ListCell	*lc, *lc1;
+
+	curr_pos = WinGetCurrentPosition(winobj);
+	elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos);
+
+	/*
+	 * Evaluate PATTERN until one of expressions is not true or out of frame.
+	 */
+	relpos = 0;
+
+	forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc));
+		char	*quantifier = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier);
+
+		/* evaluate row pattern against current row */
+		relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result);
+
+		/*
+		 * If the expression did not match, we are done.
+		 */
+		if (!expression_result)
+			break;
+
+		/* out of frame? */
+		if (relpos < 0)
+			break;
+
+		/* count up relative row position */
+		relpos++;
+	}
+
+	elog(DEBUG1, "relpos: %d", relpos);
+
+	/*
+	 * If current row satified the pattern, return argument expression.
+	 */
+	if (expression_result)
+	{
+		result = WinGetFuncArgInFrame(winobj, 0,
+									  0, WINDOW_SEEK_HEAD, false,
+									  &isnull, NULL);
+	}
+
+	/*
+	 * At this point we can set mark down to current pos -2.
+	 */
+	markpos = curr_pos -2;
+	elog(DEBUG1, "markpos: " INT64_FORMAT, markpos);
+	if (markpos >= 0)
+		WinSetMarkPosition(winobj, markpos);
+
+	if (expression_result)
+		PG_RETURN_DATUM(result);
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match relative row position
+ * -1: current row is out of frame
+ */
+static
+int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+					 int relpos, char *vname, char *quantifier, bool *result)
+{
+	ExprContext	*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell	*lc1, *lc2;
+	ExprState	*pat;
+	Datum		eval_result;
+	int			sts;
+	bool		out_of_frame = false;
+	bool		isnull;
+	StringInfo	encoded_str = makeStringInfo();
+	char		pattern_str[128];
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+		bool	second_try_match = false;
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		for (;;)
+		{
+			if (!get_slots(winobj, winstate, relpos))
+			{
+				out_of_frame = true;
+				break;	/* current row is out of frame */
+			}
+
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos);
+				break;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos);
+					break;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos);
+					appendStringInfoChar(encoded_str, vname[0]);
+
+					/* If quantifier is "+", we need to look for more matching row */
+					if (quantifier && !strcmp(quantifier, "+"))
+					{
+						/* remember that we want to try another row */
+						second_try_match = true;
+						relpos++;
+					}
+					else
+						break;
+				}
+			}
+		}
+		if (second_try_match)
+			relpos--;
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+
+		/* build regular expression */
+		snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier);
+
+		/*
+		 * Do regular expression matching against sequence of rows satisfying
+		 * the expression using regexp_instr().
+		 */
+		sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													PointerGetDatum(cstring_to_text(encoded_str->data)),
+													PointerGetDatum(cstring_to_text(pattern_str))));
+		elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s",
+			 sts, encoded_str->data, pattern_str);
+		*result = (sts > 0)? true : false;
+	}
+	return relpos;
+}
+
+/*
+ * Get current, previous and next tuple.
+ * Returns true if still within frame.
+ */
+static bool
+get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos)
+{
+	TupleTableSlot *slot;
+	bool	isnull, isout;
+	int		sts;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* for current row */
+	slot = winstate->temp_slot_1;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "current row is out of frame");
+		econtext->ecxt_scantuple = winstate->null_slot;
+		return false;
+	}
+	else
+		econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		sts = WinGetSlotInFrame(winobj, slot,
+								current_pos - 1, WINDOW_SEEK_HEAD, false,
+								&isnull, &isout);
+		if (sts < 0)
+		{
+			elog(DEBUG1, "previous row out of frame at: %d", current_pos);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		econtext->ecxt_outertuple = slot;
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos + 1, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "next row out of frame at: %d", current_pos);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+		econtext->ecxt_innertuple = slot;
+
+	return true;
+}
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..e3a9e0ffeb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,15 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'row pattern recognition in window',
+  proname => 'rpr', prokind => 'w', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_rpr' },
+{ oid => '6123', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6124', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..bc95c5fe9b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,13 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('*', '+' or '?'. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2562,11 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..a0facf38fe 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Sun_Jun_25_21_05_09_2023_126)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v1 4/7] Row pattern recognition patch (executor).
@ 2023-06-25 11:48 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-06-25 11:48 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 223 +++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  | 274 ++++++++++++++++++++++++++-
 src/include/catalog/pg_proc.dat      |   9 +
 src/include/nodes/execnodes.h        |  12 ++
 src/include/windowapi.h              |   9 +
 5 files changed, 517 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..ae997dff86 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -48,6 +48,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2692,69 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+			if (func->winfnoid != F_RPR)
+				continue;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "RPR's arg is not Var");
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2762,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2849,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2900,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3242,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3420,14 +3582,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3583,15 +3784,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3821,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..63b225271c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -39,7 +42,9 @@ typedef struct
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
-
+static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos);
+static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+							int relpos, char *vname, char *quantifier, bool *result);
 
 /*
  * utility routine for *_rank functions.
@@ -713,3 +718,270 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * rpr
+ * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in
+ * the target list.
+ * Usage: SELECT rpr(colname) OVER (..)
+ * where colname is defined in PATTERN clause.
+ */
+Datum
+window_rpr(PG_FUNCTION_ARGS)
+{
+#define	MAX_PATTERNS	16	/* max variables in PATTERN clause */
+
+	WindowObject winobj = PG_WINDOW_OBJECT();
+	WindowAggState	*winstate = WinGetAggState(winobj);
+	Datum		result;
+	bool		expression_result;
+	bool		isnull;
+	int			relpos;
+	int64		curr_pos, markpos;
+	ListCell	*lc, *lc1;
+
+	curr_pos = WinGetCurrentPosition(winobj);
+	elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos);
+
+	/*
+	 * Evaluate PATTERN until one of expressions is not true or out of frame.
+	 */
+	relpos = 0;
+
+	forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc));
+		char	*quantifier = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier);
+
+		/* evaluate row pattern against current row */
+		relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result);
+
+		/*
+		 * If the expression did not match, we are done.
+		 */
+		if (!expression_result)
+			break;
+
+		/* out of frame? */
+		if (relpos < 0)
+			break;
+
+		/* count up relative row position */
+		relpos++;
+	}
+
+	elog(DEBUG1, "relpos: %d", relpos);
+
+	/*
+	 * If current row satified the pattern, return argument expression.
+	 */
+	if (expression_result)
+	{
+		result = WinGetFuncArgInFrame(winobj, 0,
+									  0, WINDOW_SEEK_HEAD, false,
+									  &isnull, NULL);
+	}
+
+	/*
+	 * At this point we can set mark down to current pos -2.
+	 */
+	markpos = curr_pos -2;
+	elog(DEBUG1, "markpos: " INT64_FORMAT, markpos);
+	if (markpos >= 0)
+		WinSetMarkPosition(winobj, markpos);
+
+	if (expression_result)
+		PG_RETURN_DATUM(result);
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match relative row position
+ * -1: current row is out of frame
+ */
+static
+int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+					 int relpos, char *vname, char *quantifier, bool *result)
+{
+	ExprContext	*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell	*lc1, *lc2;
+	ExprState	*pat;
+	Datum		eval_result;
+	int			sts;
+	bool		out_of_frame = false;
+	bool		isnull;
+	StringInfo	encoded_str = makeStringInfo();
+	char		pattern_str[128];
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+		bool	second_try_match = false;
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		for (;;)
+		{
+			if (!get_slots(winobj, winstate, relpos))
+			{
+				out_of_frame = true;
+				break;	/* current row is out of frame */
+			}
+
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos);
+				break;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos);
+					break;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos);
+					appendStringInfoChar(encoded_str, vname[0]);
+
+					/* If quantifier is "+", we need to look for more matching row */
+					if (quantifier && !strcmp(quantifier, "+"))
+					{
+						/* remember that we want to try another row */
+						second_try_match = true;
+						relpos++;
+					}
+					else
+						break;
+				}
+			}
+		}
+		if (second_try_match)
+			relpos--;
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+
+		/* build regular expression */
+		snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier);
+
+		/*
+		 * Do regular expression matching against sequence of rows satisfying
+		 * the expression using regexp_instr().
+		 */
+		sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													PointerGetDatum(cstring_to_text(encoded_str->data)),
+													PointerGetDatum(cstring_to_text(pattern_str))));
+		elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s",
+			 sts, encoded_str->data, pattern_str);
+		*result = (sts > 0)? true : false;
+	}
+	return relpos;
+}
+
+/*
+ * Get current, previous and next tuple.
+ * Returns true if still within frame.
+ */
+static bool
+get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos)
+{
+	TupleTableSlot *slot;
+	bool	isnull, isout;
+	int		sts;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* for current row */
+	slot = winstate->temp_slot_1;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "current row is out of frame");
+		econtext->ecxt_scantuple = winstate->null_slot;
+		return false;
+	}
+	else
+		econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		sts = WinGetSlotInFrame(winobj, slot,
+								current_pos - 1, WINDOW_SEEK_HEAD, false,
+								&isnull, &isout);
+		if (sts < 0)
+		{
+			elog(DEBUG1, "previous row out of frame at: %d", current_pos);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		econtext->ecxt_outertuple = slot;
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos + 1, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "next row out of frame at: %d", current_pos);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+		econtext->ecxt_innertuple = slot;
+
+	return true;
+}
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..e3a9e0ffeb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,15 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'row pattern recognition in window',
+  proname => 'rpr', prokind => 'w', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_rpr' },
+{ oid => '6123', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6124', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..bc95c5fe9b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,13 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('*', '+' or '?'. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2562,11 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..a0facf38fe 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Sun_Jun_25_21_05_09_2023_126)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v1 4/7] Row pattern recognition patch (executor).
@ 2023-06-25 11:48 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-06-25 11:48 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 223 +++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  | 274 ++++++++++++++++++++++++++-
 src/include/catalog/pg_proc.dat      |   9 +
 src/include/nodes/execnodes.h        |  12 ++
 src/include/windowapi.h              |   9 +
 5 files changed, 517 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..ae997dff86 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -48,6 +48,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2692,69 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+			if (func->winfnoid != F_RPR)
+				continue;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "RPR's arg is not Var");
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2762,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2849,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2900,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3242,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3420,14 +3582,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3583,15 +3784,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3821,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..63b225271c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -39,7 +42,9 @@ typedef struct
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
-
+static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos);
+static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+							int relpos, char *vname, char *quantifier, bool *result);
 
 /*
  * utility routine for *_rank functions.
@@ -713,3 +718,270 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * rpr
+ * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in
+ * the target list.
+ * Usage: SELECT rpr(colname) OVER (..)
+ * where colname is defined in PATTERN clause.
+ */
+Datum
+window_rpr(PG_FUNCTION_ARGS)
+{
+#define	MAX_PATTERNS	16	/* max variables in PATTERN clause */
+
+	WindowObject winobj = PG_WINDOW_OBJECT();
+	WindowAggState	*winstate = WinGetAggState(winobj);
+	Datum		result;
+	bool		expression_result;
+	bool		isnull;
+	int			relpos;
+	int64		curr_pos, markpos;
+	ListCell	*lc, *lc1;
+
+	curr_pos = WinGetCurrentPosition(winobj);
+	elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos);
+
+	/*
+	 * Evaluate PATTERN until one of expressions is not true or out of frame.
+	 */
+	relpos = 0;
+
+	forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc));
+		char	*quantifier = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier);
+
+		/* evaluate row pattern against current row */
+		relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result);
+
+		/*
+		 * If the expression did not match, we are done.
+		 */
+		if (!expression_result)
+			break;
+
+		/* out of frame? */
+		if (relpos < 0)
+			break;
+
+		/* count up relative row position */
+		relpos++;
+	}
+
+	elog(DEBUG1, "relpos: %d", relpos);
+
+	/*
+	 * If current row satified the pattern, return argument expression.
+	 */
+	if (expression_result)
+	{
+		result = WinGetFuncArgInFrame(winobj, 0,
+									  0, WINDOW_SEEK_HEAD, false,
+									  &isnull, NULL);
+	}
+
+	/*
+	 * At this point we can set mark down to current pos -2.
+	 */
+	markpos = curr_pos -2;
+	elog(DEBUG1, "markpos: " INT64_FORMAT, markpos);
+	if (markpos >= 0)
+		WinSetMarkPosition(winobj, markpos);
+
+	if (expression_result)
+		PG_RETURN_DATUM(result);
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match relative row position
+ * -1: current row is out of frame
+ */
+static
+int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+					 int relpos, char *vname, char *quantifier, bool *result)
+{
+	ExprContext	*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell	*lc1, *lc2;
+	ExprState	*pat;
+	Datum		eval_result;
+	int			sts;
+	bool		out_of_frame = false;
+	bool		isnull;
+	StringInfo	encoded_str = makeStringInfo();
+	char		pattern_str[128];
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+		bool	second_try_match = false;
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		for (;;)
+		{
+			if (!get_slots(winobj, winstate, relpos))
+			{
+				out_of_frame = true;
+				break;	/* current row is out of frame */
+			}
+
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos);
+				break;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos);
+					break;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos);
+					appendStringInfoChar(encoded_str, vname[0]);
+
+					/* If quantifier is "+", we need to look for more matching row */
+					if (quantifier && !strcmp(quantifier, "+"))
+					{
+						/* remember that we want to try another row */
+						second_try_match = true;
+						relpos++;
+					}
+					else
+						break;
+				}
+			}
+		}
+		if (second_try_match)
+			relpos--;
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+
+		/* build regular expression */
+		snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier);
+
+		/*
+		 * Do regular expression matching against sequence of rows satisfying
+		 * the expression using regexp_instr().
+		 */
+		sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													PointerGetDatum(cstring_to_text(encoded_str->data)),
+													PointerGetDatum(cstring_to_text(pattern_str))));
+		elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s",
+			 sts, encoded_str->data, pattern_str);
+		*result = (sts > 0)? true : false;
+	}
+	return relpos;
+}
+
+/*
+ * Get current, previous and next tuple.
+ * Returns true if still within frame.
+ */
+static bool
+get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos)
+{
+	TupleTableSlot *slot;
+	bool	isnull, isout;
+	int		sts;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* for current row */
+	slot = winstate->temp_slot_1;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "current row is out of frame");
+		econtext->ecxt_scantuple = winstate->null_slot;
+		return false;
+	}
+	else
+		econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		sts = WinGetSlotInFrame(winobj, slot,
+								current_pos - 1, WINDOW_SEEK_HEAD, false,
+								&isnull, &isout);
+		if (sts < 0)
+		{
+			elog(DEBUG1, "previous row out of frame at: %d", current_pos);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		econtext->ecxt_outertuple = slot;
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos + 1, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "next row out of frame at: %d", current_pos);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+		econtext->ecxt_innertuple = slot;
+
+	return true;
+}
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..e3a9e0ffeb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,15 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'row pattern recognition in window',
+  proname => 'rpr', prokind => 'w', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_rpr' },
+{ oid => '6123', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6124', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..bc95c5fe9b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,13 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('*', '+' or '?'. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2562,11 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..a0facf38fe 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Sun_Jun_25_21_05_09_2023_126)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v1 4/7] Row pattern recognition patch (executor).
@ 2023-06-25 11:48 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-06-25 11:48 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 223 +++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  | 274 ++++++++++++++++++++++++++-
 src/include/catalog/pg_proc.dat      |   9 +
 src/include/nodes/execnodes.h        |  12 ++
 src/include/windowapi.h              |   9 +
 5 files changed, 517 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..ae997dff86 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -48,6 +48,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2692,69 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+			if (func->winfnoid != F_RPR)
+				continue;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "RPR's arg is not Var");
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2762,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2849,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2900,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3242,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3420,14 +3582,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3583,15 +3784,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3821,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..63b225271c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -39,7 +42,9 @@ typedef struct
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
-
+static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos);
+static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+							int relpos, char *vname, char *quantifier, bool *result);
 
 /*
  * utility routine for *_rank functions.
@@ -713,3 +718,270 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * rpr
+ * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in
+ * the target list.
+ * Usage: SELECT rpr(colname) OVER (..)
+ * where colname is defined in PATTERN clause.
+ */
+Datum
+window_rpr(PG_FUNCTION_ARGS)
+{
+#define	MAX_PATTERNS	16	/* max variables in PATTERN clause */
+
+	WindowObject winobj = PG_WINDOW_OBJECT();
+	WindowAggState	*winstate = WinGetAggState(winobj);
+	Datum		result;
+	bool		expression_result;
+	bool		isnull;
+	int			relpos;
+	int64		curr_pos, markpos;
+	ListCell	*lc, *lc1;
+
+	curr_pos = WinGetCurrentPosition(winobj);
+	elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos);
+
+	/*
+	 * Evaluate PATTERN until one of expressions is not true or out of frame.
+	 */
+	relpos = 0;
+
+	forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc));
+		char	*quantifier = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier);
+
+		/* evaluate row pattern against current row */
+		relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result);
+
+		/*
+		 * If the expression did not match, we are done.
+		 */
+		if (!expression_result)
+			break;
+
+		/* out of frame? */
+		if (relpos < 0)
+			break;
+
+		/* count up relative row position */
+		relpos++;
+	}
+
+	elog(DEBUG1, "relpos: %d", relpos);
+
+	/*
+	 * If current row satified the pattern, return argument expression.
+	 */
+	if (expression_result)
+	{
+		result = WinGetFuncArgInFrame(winobj, 0,
+									  0, WINDOW_SEEK_HEAD, false,
+									  &isnull, NULL);
+	}
+
+	/*
+	 * At this point we can set mark down to current pos -2.
+	 */
+	markpos = curr_pos -2;
+	elog(DEBUG1, "markpos: " INT64_FORMAT, markpos);
+	if (markpos >= 0)
+		WinSetMarkPosition(winobj, markpos);
+
+	if (expression_result)
+		PG_RETURN_DATUM(result);
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match relative row position
+ * -1: current row is out of frame
+ */
+static
+int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+					 int relpos, char *vname, char *quantifier, bool *result)
+{
+	ExprContext	*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell	*lc1, *lc2;
+	ExprState	*pat;
+	Datum		eval_result;
+	int			sts;
+	bool		out_of_frame = false;
+	bool		isnull;
+	StringInfo	encoded_str = makeStringInfo();
+	char		pattern_str[128];
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+		bool	second_try_match = false;
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		for (;;)
+		{
+			if (!get_slots(winobj, winstate, relpos))
+			{
+				out_of_frame = true;
+				break;	/* current row is out of frame */
+			}
+
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos);
+				break;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos);
+					break;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos);
+					appendStringInfoChar(encoded_str, vname[0]);
+
+					/* If quantifier is "+", we need to look for more matching row */
+					if (quantifier && !strcmp(quantifier, "+"))
+					{
+						/* remember that we want to try another row */
+						second_try_match = true;
+						relpos++;
+					}
+					else
+						break;
+				}
+			}
+		}
+		if (second_try_match)
+			relpos--;
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+
+		/* build regular expression */
+		snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier);
+
+		/*
+		 * Do regular expression matching against sequence of rows satisfying
+		 * the expression using regexp_instr().
+		 */
+		sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													PointerGetDatum(cstring_to_text(encoded_str->data)),
+													PointerGetDatum(cstring_to_text(pattern_str))));
+		elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s",
+			 sts, encoded_str->data, pattern_str);
+		*result = (sts > 0)? true : false;
+	}
+	return relpos;
+}
+
+/*
+ * Get current, previous and next tuple.
+ * Returns true if still within frame.
+ */
+static bool
+get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos)
+{
+	TupleTableSlot *slot;
+	bool	isnull, isout;
+	int		sts;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* for current row */
+	slot = winstate->temp_slot_1;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "current row is out of frame");
+		econtext->ecxt_scantuple = winstate->null_slot;
+		return false;
+	}
+	else
+		econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		sts = WinGetSlotInFrame(winobj, slot,
+								current_pos - 1, WINDOW_SEEK_HEAD, false,
+								&isnull, &isout);
+		if (sts < 0)
+		{
+			elog(DEBUG1, "previous row out of frame at: %d", current_pos);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		econtext->ecxt_outertuple = slot;
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos + 1, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "next row out of frame at: %d", current_pos);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+		econtext->ecxt_innertuple = slot;
+
+	return true;
+}
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..e3a9e0ffeb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,15 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'row pattern recognition in window',
+  proname => 'rpr', prokind => 'w', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_rpr' },
+{ oid => '6123', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6124', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..bc95c5fe9b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,13 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('*', '+' or '?'. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2562,11 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..a0facf38fe 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Sun_Jun_25_21_05_09_2023_126)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v1-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v2 4/7] Row pattern recognition patch (executor).
@ 2023-06-26 08:05 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-06-26 08:05 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 225 +++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  | 302 ++++++++++++++++++++++++++-
 src/include/catalog/pg_proc.dat      |   9 +
 src/include/nodes/execnodes.h        |  13 ++
 src/include/windowapi.h              |   9 +
 5 files changed, 548 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..bef2bc62b2 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -48,6 +48,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2692,71 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+			if (func->winfnoid != F_RPR)
+				continue;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "RPR's arg is not Var");
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2764,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2851,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2902,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3244,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3420,14 +3584,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3583,15 +3786,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3823,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..74ef11ce55 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,10 +39,21 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
-
+static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos);
+static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+							int relpos, char *vname, char *quantifier, bool *result);
 
 /*
  * utility routine for *_rank functions.
@@ -713,3 +727,289 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * rpr
+ * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in
+ * the target list.
+ * Usage: SELECT rpr(colname) OVER (..)
+ * where colname is defined in PATTERN clause.
+ */
+Datum
+window_rpr(PG_FUNCTION_ARGS)
+{
+#define	MAX_PATTERNS	16	/* max variables in PATTERN clause */
+
+	WindowObject winobj = PG_WINDOW_OBJECT();
+	WindowAggState	*winstate = WinGetAggState(winobj);
+	Datum		result;
+	bool		expression_result;
+	bool		isnull;
+	int			relpos;
+	int64		curr_pos, markpos;
+	ListCell	*lc, *lc1;
+	SkipContext	*context = NULL;
+
+	curr_pos = WinGetCurrentPosition(winobj);
+	elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos);
+
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+	{
+		context = (SkipContext *) WinGetPartitionLocalMemory(winobj, sizeof(SkipContext));
+		if (curr_pos < context->pos)
+		{
+			elog(DEBUG1, "skip this row: curr_pos: " INT64_FORMAT "context->pos: " INT64_FORMAT,
+				 curr_pos, context->pos);
+			PG_RETURN_NULL();
+		}
+	}
+
+	/*
+	 * Evaluate PATTERN until one of expressions is not true or out of frame.
+	 */
+	relpos = 0;
+
+	forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc));
+		char	*quantifier = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier);
+
+		/* evaluate row pattern against current row */
+		relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result);
+
+		/*
+		 * If the expression did not match, we are done.
+		 */
+		if (!expression_result)
+			break;
+
+		/* out of frame? */
+		if (relpos < 0)
+			break;
+
+		/* count up relative row position */
+		relpos++;
+	}
+
+	elog(DEBUG1, "relpos: %d", relpos);
+
+	/*
+	 * If current row satified the pattern, return argument expression.
+	 */
+	if (expression_result)
+	{
+		result = WinGetFuncArgInFrame(winobj, 0,
+									  0, WINDOW_SEEK_HEAD, false,
+									  &isnull, NULL);
+	}
+
+	/*
+	 * At this point we can set mark down to current pos -2.
+	 */
+	markpos = curr_pos -2;
+	elog(DEBUG1, "markpos: " INT64_FORMAT, markpos);
+	if (markpos >= 0)
+		WinSetMarkPosition(winobj, markpos);
+
+	if (expression_result)
+	{
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+		{
+			context->pos += relpos;
+			elog(DEBUG1, "context->pos: " INT64_FORMAT, context->pos);
+		}
+		PG_RETURN_DATUM(result);
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match relative row position
+ * -1: current row is out of frame
+ */
+static
+int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+					 int relpos, char *vname, char *quantifier, bool *result)
+{
+	ExprContext	*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell	*lc1, *lc2;
+	ExprState	*pat;
+	Datum		eval_result;
+	int			sts;
+	bool		out_of_frame = false;
+	bool		isnull;
+	StringInfo	encoded_str = makeStringInfo();
+	char		pattern_str[128];
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+		bool	second_try_match = false;
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		for (;;)
+		{
+			if (!get_slots(winobj, winstate, relpos))
+			{
+				out_of_frame = true;
+				break;	/* current row is out of frame */
+			}
+
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos);
+				break;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos);
+					break;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos);
+					appendStringInfoChar(encoded_str, vname[0]);
+
+					/* If quantifier is "+", we need to look for more matching row */
+					if (quantifier && !strcmp(quantifier, "+"))
+					{
+						/* remember that we want to try another row */
+						second_try_match = true;
+						relpos++;
+					}
+					else
+						break;
+				}
+			}
+		}
+		if (second_try_match)
+			relpos--;
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+
+		/* build regular expression */
+		snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier);
+
+		/*
+		 * Do regular expression matching against sequence of rows satisfying
+		 * the expression using regexp_instr().
+		 */
+		sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													PointerGetDatum(cstring_to_text(encoded_str->data)),
+													PointerGetDatum(cstring_to_text(pattern_str))));
+		elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s",
+			 sts, encoded_str->data, pattern_str);
+		*result = (sts > 0)? true : false;
+	}
+	return relpos;
+}
+
+/*
+ * Get current, previous and next tuple.
+ * Returns true if still within frame.
+ */
+static bool
+get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos)
+{
+	TupleTableSlot *slot;
+	bool	isnull, isout;
+	int		sts;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* for current row */
+	slot = winstate->temp_slot_1;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "current row is out of frame");
+		econtext->ecxt_scantuple = winstate->null_slot;
+		return false;
+	}
+	else
+		econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		sts = WinGetSlotInFrame(winobj, slot,
+								current_pos - 1, WINDOW_SEEK_HEAD, false,
+								&isnull, &isout);
+		if (sts < 0)
+		{
+			elog(DEBUG1, "previous row out of frame at: %d", current_pos);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		econtext->ecxt_outertuple = slot;
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos + 1, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "next row out of frame at: %d", current_pos);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+		econtext->ecxt_innertuple = slot;
+
+	return true;
+}
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..e3a9e0ffeb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,15 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'row pattern recognition in window',
+  proname => 'rpr', prokind => 'w', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_rpr' },
+{ oid => '6123', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6124', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..1643eaa6f1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,14 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2563,11 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..a0facf38fe 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Mon_Jun_26_17_45_07_2023_724)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v2-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v2 4/7] Row pattern recognition patch (executor).
@ 2023-06-26 08:05 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-06-26 08:05 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 225 +++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  | 302 ++++++++++++++++++++++++++-
 src/include/catalog/pg_proc.dat      |   9 +
 src/include/nodes/execnodes.h        |  13 ++
 src/include/windowapi.h              |   9 +
 5 files changed, 548 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..bef2bc62b2 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -48,6 +48,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2692,71 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+			if (func->winfnoid != F_RPR)
+				continue;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "RPR's arg is not Var");
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2764,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2851,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2902,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3244,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3420,14 +3584,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3583,15 +3786,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3823,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..74ef11ce55 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,10 +39,21 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
-
+static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos);
+static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+							int relpos, char *vname, char *quantifier, bool *result);
 
 /*
  * utility routine for *_rank functions.
@@ -713,3 +727,289 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * rpr
+ * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in
+ * the target list.
+ * Usage: SELECT rpr(colname) OVER (..)
+ * where colname is defined in PATTERN clause.
+ */
+Datum
+window_rpr(PG_FUNCTION_ARGS)
+{
+#define	MAX_PATTERNS	16	/* max variables in PATTERN clause */
+
+	WindowObject winobj = PG_WINDOW_OBJECT();
+	WindowAggState	*winstate = WinGetAggState(winobj);
+	Datum		result;
+	bool		expression_result;
+	bool		isnull;
+	int			relpos;
+	int64		curr_pos, markpos;
+	ListCell	*lc, *lc1;
+	SkipContext	*context = NULL;
+
+	curr_pos = WinGetCurrentPosition(winobj);
+	elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos);
+
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+	{
+		context = (SkipContext *) WinGetPartitionLocalMemory(winobj, sizeof(SkipContext));
+		if (curr_pos < context->pos)
+		{
+			elog(DEBUG1, "skip this row: curr_pos: " INT64_FORMAT "context->pos: " INT64_FORMAT,
+				 curr_pos, context->pos);
+			PG_RETURN_NULL();
+		}
+	}
+
+	/*
+	 * Evaluate PATTERN until one of expressions is not true or out of frame.
+	 */
+	relpos = 0;
+
+	forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc));
+		char	*quantifier = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier);
+
+		/* evaluate row pattern against current row */
+		relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result);
+
+		/*
+		 * If the expression did not match, we are done.
+		 */
+		if (!expression_result)
+			break;
+
+		/* out of frame? */
+		if (relpos < 0)
+			break;
+
+		/* count up relative row position */
+		relpos++;
+	}
+
+	elog(DEBUG1, "relpos: %d", relpos);
+
+	/*
+	 * If current row satified the pattern, return argument expression.
+	 */
+	if (expression_result)
+	{
+		result = WinGetFuncArgInFrame(winobj, 0,
+									  0, WINDOW_SEEK_HEAD, false,
+									  &isnull, NULL);
+	}
+
+	/*
+	 * At this point we can set mark down to current pos -2.
+	 */
+	markpos = curr_pos -2;
+	elog(DEBUG1, "markpos: " INT64_FORMAT, markpos);
+	if (markpos >= 0)
+		WinSetMarkPosition(winobj, markpos);
+
+	if (expression_result)
+	{
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+		{
+			context->pos += relpos;
+			elog(DEBUG1, "context->pos: " INT64_FORMAT, context->pos);
+		}
+		PG_RETURN_DATUM(result);
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match relative row position
+ * -1: current row is out of frame
+ */
+static
+int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+					 int relpos, char *vname, char *quantifier, bool *result)
+{
+	ExprContext	*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell	*lc1, *lc2;
+	ExprState	*pat;
+	Datum		eval_result;
+	int			sts;
+	bool		out_of_frame = false;
+	bool		isnull;
+	StringInfo	encoded_str = makeStringInfo();
+	char		pattern_str[128];
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+		bool	second_try_match = false;
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		for (;;)
+		{
+			if (!get_slots(winobj, winstate, relpos))
+			{
+				out_of_frame = true;
+				break;	/* current row is out of frame */
+			}
+
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos);
+				break;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos);
+					break;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos);
+					appendStringInfoChar(encoded_str, vname[0]);
+
+					/* If quantifier is "+", we need to look for more matching row */
+					if (quantifier && !strcmp(quantifier, "+"))
+					{
+						/* remember that we want to try another row */
+						second_try_match = true;
+						relpos++;
+					}
+					else
+						break;
+				}
+			}
+		}
+		if (second_try_match)
+			relpos--;
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+
+		/* build regular expression */
+		snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier);
+
+		/*
+		 * Do regular expression matching against sequence of rows satisfying
+		 * the expression using regexp_instr().
+		 */
+		sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													PointerGetDatum(cstring_to_text(encoded_str->data)),
+													PointerGetDatum(cstring_to_text(pattern_str))));
+		elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s",
+			 sts, encoded_str->data, pattern_str);
+		*result = (sts > 0)? true : false;
+	}
+	return relpos;
+}
+
+/*
+ * Get current, previous and next tuple.
+ * Returns true if still within frame.
+ */
+static bool
+get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos)
+{
+	TupleTableSlot *slot;
+	bool	isnull, isout;
+	int		sts;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* for current row */
+	slot = winstate->temp_slot_1;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "current row is out of frame");
+		econtext->ecxt_scantuple = winstate->null_slot;
+		return false;
+	}
+	else
+		econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		sts = WinGetSlotInFrame(winobj, slot,
+								current_pos - 1, WINDOW_SEEK_HEAD, false,
+								&isnull, &isout);
+		if (sts < 0)
+		{
+			elog(DEBUG1, "previous row out of frame at: %d", current_pos);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		econtext->ecxt_outertuple = slot;
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos + 1, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "next row out of frame at: %d", current_pos);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+		econtext->ecxt_innertuple = slot;
+
+	return true;
+}
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..e3a9e0ffeb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,15 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'row pattern recognition in window',
+  proname => 'rpr', prokind => 'w', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_rpr' },
+{ oid => '6123', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6124', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..1643eaa6f1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,14 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2563,11 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..a0facf38fe 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Mon_Jun_26_17_45_07_2023_724)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v2-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v2 4/7] Row pattern recognition patch (executor).
@ 2023-06-26 08:05 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-06-26 08:05 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 225 +++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  | 302 ++++++++++++++++++++++++++-
 src/include/catalog/pg_proc.dat      |   9 +
 src/include/nodes/execnodes.h        |  13 ++
 src/include/windowapi.h              |   9 +
 5 files changed, 548 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..bef2bc62b2 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -48,6 +48,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2692,71 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+			if (func->winfnoid != F_RPR)
+				continue;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "RPR's arg is not Var");
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2764,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2851,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2902,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3244,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3420,14 +3584,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3583,15 +3786,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3823,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..74ef11ce55 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,10 +39,21 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
-
+static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos);
+static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+							int relpos, char *vname, char *quantifier, bool *result);
 
 /*
  * utility routine for *_rank functions.
@@ -713,3 +727,289 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * rpr
+ * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in
+ * the target list.
+ * Usage: SELECT rpr(colname) OVER (..)
+ * where colname is defined in PATTERN clause.
+ */
+Datum
+window_rpr(PG_FUNCTION_ARGS)
+{
+#define	MAX_PATTERNS	16	/* max variables in PATTERN clause */
+
+	WindowObject winobj = PG_WINDOW_OBJECT();
+	WindowAggState	*winstate = WinGetAggState(winobj);
+	Datum		result;
+	bool		expression_result;
+	bool		isnull;
+	int			relpos;
+	int64		curr_pos, markpos;
+	ListCell	*lc, *lc1;
+	SkipContext	*context = NULL;
+
+	curr_pos = WinGetCurrentPosition(winobj);
+	elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos);
+
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+	{
+		context = (SkipContext *) WinGetPartitionLocalMemory(winobj, sizeof(SkipContext));
+		if (curr_pos < context->pos)
+		{
+			elog(DEBUG1, "skip this row: curr_pos: " INT64_FORMAT "context->pos: " INT64_FORMAT,
+				 curr_pos, context->pos);
+			PG_RETURN_NULL();
+		}
+	}
+
+	/*
+	 * Evaluate PATTERN until one of expressions is not true or out of frame.
+	 */
+	relpos = 0;
+
+	forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc));
+		char	*quantifier = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier);
+
+		/* evaluate row pattern against current row */
+		relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result);
+
+		/*
+		 * If the expression did not match, we are done.
+		 */
+		if (!expression_result)
+			break;
+
+		/* out of frame? */
+		if (relpos < 0)
+			break;
+
+		/* count up relative row position */
+		relpos++;
+	}
+
+	elog(DEBUG1, "relpos: %d", relpos);
+
+	/*
+	 * If current row satified the pattern, return argument expression.
+	 */
+	if (expression_result)
+	{
+		result = WinGetFuncArgInFrame(winobj, 0,
+									  0, WINDOW_SEEK_HEAD, false,
+									  &isnull, NULL);
+	}
+
+	/*
+	 * At this point we can set mark down to current pos -2.
+	 */
+	markpos = curr_pos -2;
+	elog(DEBUG1, "markpos: " INT64_FORMAT, markpos);
+	if (markpos >= 0)
+		WinSetMarkPosition(winobj, markpos);
+
+	if (expression_result)
+	{
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+		{
+			context->pos += relpos;
+			elog(DEBUG1, "context->pos: " INT64_FORMAT, context->pos);
+		}
+		PG_RETURN_DATUM(result);
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match relative row position
+ * -1: current row is out of frame
+ */
+static
+int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+					 int relpos, char *vname, char *quantifier, bool *result)
+{
+	ExprContext	*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell	*lc1, *lc2;
+	ExprState	*pat;
+	Datum		eval_result;
+	int			sts;
+	bool		out_of_frame = false;
+	bool		isnull;
+	StringInfo	encoded_str = makeStringInfo();
+	char		pattern_str[128];
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+		bool	second_try_match = false;
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		for (;;)
+		{
+			if (!get_slots(winobj, winstate, relpos))
+			{
+				out_of_frame = true;
+				break;	/* current row is out of frame */
+			}
+
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos);
+				break;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos);
+					break;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos);
+					appendStringInfoChar(encoded_str, vname[0]);
+
+					/* If quantifier is "+", we need to look for more matching row */
+					if (quantifier && !strcmp(quantifier, "+"))
+					{
+						/* remember that we want to try another row */
+						second_try_match = true;
+						relpos++;
+					}
+					else
+						break;
+				}
+			}
+		}
+		if (second_try_match)
+			relpos--;
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+
+		/* build regular expression */
+		snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier);
+
+		/*
+		 * Do regular expression matching against sequence of rows satisfying
+		 * the expression using regexp_instr().
+		 */
+		sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													PointerGetDatum(cstring_to_text(encoded_str->data)),
+													PointerGetDatum(cstring_to_text(pattern_str))));
+		elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s",
+			 sts, encoded_str->data, pattern_str);
+		*result = (sts > 0)? true : false;
+	}
+	return relpos;
+}
+
+/*
+ * Get current, previous and next tuple.
+ * Returns true if still within frame.
+ */
+static bool
+get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos)
+{
+	TupleTableSlot *slot;
+	bool	isnull, isout;
+	int		sts;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* for current row */
+	slot = winstate->temp_slot_1;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "current row is out of frame");
+		econtext->ecxt_scantuple = winstate->null_slot;
+		return false;
+	}
+	else
+		econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		sts = WinGetSlotInFrame(winobj, slot,
+								current_pos - 1, WINDOW_SEEK_HEAD, false,
+								&isnull, &isout);
+		if (sts < 0)
+		{
+			elog(DEBUG1, "previous row out of frame at: %d", current_pos);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		econtext->ecxt_outertuple = slot;
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos + 1, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "next row out of frame at: %d", current_pos);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+		econtext->ecxt_innertuple = slot;
+
+	return true;
+}
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..e3a9e0ffeb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,15 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'row pattern recognition in window',
+  proname => 'rpr', prokind => 'w', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_rpr' },
+{ oid => '6123', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6124', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..1643eaa6f1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,14 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2563,11 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..a0facf38fe 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Mon_Jun_26_17_45_07_2023_724)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v2-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v2 4/7] Row pattern recognition patch (executor).
@ 2023-06-26 08:05 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-06-26 08:05 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 225 +++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  | 302 ++++++++++++++++++++++++++-
 src/include/catalog/pg_proc.dat      |   9 +
 src/include/nodes/execnodes.h        |  13 ++
 src/include/windowapi.h              |   9 +
 5 files changed, 548 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..bef2bc62b2 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -48,6 +48,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2692,71 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+			if (func->winfnoid != F_RPR)
+				continue;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "RPR's arg is not Var");
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2764,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2851,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2902,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3244,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3420,14 +3584,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3583,15 +3786,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3823,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..74ef11ce55 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,10 +39,21 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
-
+static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos);
+static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+							int relpos, char *vname, char *quantifier, bool *result);
 
 /*
  * utility routine for *_rank functions.
@@ -713,3 +727,289 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * rpr
+ * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in
+ * the target list.
+ * Usage: SELECT rpr(colname) OVER (..)
+ * where colname is defined in PATTERN clause.
+ */
+Datum
+window_rpr(PG_FUNCTION_ARGS)
+{
+#define	MAX_PATTERNS	16	/* max variables in PATTERN clause */
+
+	WindowObject winobj = PG_WINDOW_OBJECT();
+	WindowAggState	*winstate = WinGetAggState(winobj);
+	Datum		result;
+	bool		expression_result;
+	bool		isnull;
+	int			relpos;
+	int64		curr_pos, markpos;
+	ListCell	*lc, *lc1;
+	SkipContext	*context = NULL;
+
+	curr_pos = WinGetCurrentPosition(winobj);
+	elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos);
+
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+	{
+		context = (SkipContext *) WinGetPartitionLocalMemory(winobj, sizeof(SkipContext));
+		if (curr_pos < context->pos)
+		{
+			elog(DEBUG1, "skip this row: curr_pos: " INT64_FORMAT "context->pos: " INT64_FORMAT,
+				 curr_pos, context->pos);
+			PG_RETURN_NULL();
+		}
+	}
+
+	/*
+	 * Evaluate PATTERN until one of expressions is not true or out of frame.
+	 */
+	relpos = 0;
+
+	forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc));
+		char	*quantifier = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier);
+
+		/* evaluate row pattern against current row */
+		relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result);
+
+		/*
+		 * If the expression did not match, we are done.
+		 */
+		if (!expression_result)
+			break;
+
+		/* out of frame? */
+		if (relpos < 0)
+			break;
+
+		/* count up relative row position */
+		relpos++;
+	}
+
+	elog(DEBUG1, "relpos: %d", relpos);
+
+	/*
+	 * If current row satified the pattern, return argument expression.
+	 */
+	if (expression_result)
+	{
+		result = WinGetFuncArgInFrame(winobj, 0,
+									  0, WINDOW_SEEK_HEAD, false,
+									  &isnull, NULL);
+	}
+
+	/*
+	 * At this point we can set mark down to current pos -2.
+	 */
+	markpos = curr_pos -2;
+	elog(DEBUG1, "markpos: " INT64_FORMAT, markpos);
+	if (markpos >= 0)
+		WinSetMarkPosition(winobj, markpos);
+
+	if (expression_result)
+	{
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+		{
+			context->pos += relpos;
+			elog(DEBUG1, "context->pos: " INT64_FORMAT, context->pos);
+		}
+		PG_RETURN_DATUM(result);
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match relative row position
+ * -1: current row is out of frame
+ */
+static
+int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+					 int relpos, char *vname, char *quantifier, bool *result)
+{
+	ExprContext	*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell	*lc1, *lc2;
+	ExprState	*pat;
+	Datum		eval_result;
+	int			sts;
+	bool		out_of_frame = false;
+	bool		isnull;
+	StringInfo	encoded_str = makeStringInfo();
+	char		pattern_str[128];
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+		bool	second_try_match = false;
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		for (;;)
+		{
+			if (!get_slots(winobj, winstate, relpos))
+			{
+				out_of_frame = true;
+				break;	/* current row is out of frame */
+			}
+
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos);
+				break;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos);
+					break;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos);
+					appendStringInfoChar(encoded_str, vname[0]);
+
+					/* If quantifier is "+", we need to look for more matching row */
+					if (quantifier && !strcmp(quantifier, "+"))
+					{
+						/* remember that we want to try another row */
+						second_try_match = true;
+						relpos++;
+					}
+					else
+						break;
+				}
+			}
+		}
+		if (second_try_match)
+			relpos--;
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+
+		/* build regular expression */
+		snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier);
+
+		/*
+		 * Do regular expression matching against sequence of rows satisfying
+		 * the expression using regexp_instr().
+		 */
+		sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													PointerGetDatum(cstring_to_text(encoded_str->data)),
+													PointerGetDatum(cstring_to_text(pattern_str))));
+		elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s",
+			 sts, encoded_str->data, pattern_str);
+		*result = (sts > 0)? true : false;
+	}
+	return relpos;
+}
+
+/*
+ * Get current, previous and next tuple.
+ * Returns true if still within frame.
+ */
+static bool
+get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos)
+{
+	TupleTableSlot *slot;
+	bool	isnull, isout;
+	int		sts;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* for current row */
+	slot = winstate->temp_slot_1;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "current row is out of frame");
+		econtext->ecxt_scantuple = winstate->null_slot;
+		return false;
+	}
+	else
+		econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		sts = WinGetSlotInFrame(winobj, slot,
+								current_pos - 1, WINDOW_SEEK_HEAD, false,
+								&isnull, &isout);
+		if (sts < 0)
+		{
+			elog(DEBUG1, "previous row out of frame at: %d", current_pos);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		econtext->ecxt_outertuple = slot;
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos + 1, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "next row out of frame at: %d", current_pos);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+		econtext->ecxt_innertuple = slot;
+
+	return true;
+}
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..e3a9e0ffeb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,15 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'row pattern recognition in window',
+  proname => 'rpr', prokind => 'w', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_rpr' },
+{ oid => '6123', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6124', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..1643eaa6f1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,14 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2563,11 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..a0facf38fe 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Mon_Jun_26_17_45_07_2023_724)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v2-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v2 4/7] Row pattern recognition patch (executor).
@ 2023-06-26 08:05 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-06-26 08:05 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 225 +++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  | 302 ++++++++++++++++++++++++++-
 src/include/catalog/pg_proc.dat      |   9 +
 src/include/nodes/execnodes.h        |  13 ++
 src/include/windowapi.h              |   9 +
 5 files changed, 548 insertions(+), 10 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..bef2bc62b2 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -48,6 +48,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +160,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -195,9 +204,9 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -2388,6 +2397,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2498,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2692,71 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+			if (func->winfnoid != F_RPR)
+				continue;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "RPR must have 1 argument but function %d has %d args", func->winfnoid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "RPR's arg is not Var");
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2764,76 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2851,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2902,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3244,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3420,14 +3584,53 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3583,15 +3786,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3823,9 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..74ef11ce55 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,10 +39,21 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
-
+static bool get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos);
+static int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+							int relpos, char *vname, char *quantifier, bool *result);
 
 /*
  * utility routine for *_rank functions.
@@ -713,3 +727,289 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * rpr
+ * allow to use "Row pattern recognition: WINDOW clause" (SQL:2016 R020) in
+ * the target list.
+ * Usage: SELECT rpr(colname) OVER (..)
+ * where colname is defined in PATTERN clause.
+ */
+Datum
+window_rpr(PG_FUNCTION_ARGS)
+{
+#define	MAX_PATTERNS	16	/* max variables in PATTERN clause */
+
+	WindowObject winobj = PG_WINDOW_OBJECT();
+	WindowAggState	*winstate = WinGetAggState(winobj);
+	Datum		result;
+	bool		expression_result;
+	bool		isnull;
+	int			relpos;
+	int64		curr_pos, markpos;
+	ListCell	*lc, *lc1;
+	SkipContext	*context = NULL;
+
+	curr_pos = WinGetCurrentPosition(winobj);
+	elog(DEBUG1, "rpr is called. row: " INT64_FORMAT, curr_pos);
+
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+	{
+		context = (SkipContext *) WinGetPartitionLocalMemory(winobj, sizeof(SkipContext));
+		if (curr_pos < context->pos)
+		{
+			elog(DEBUG1, "skip this row: curr_pos: " INT64_FORMAT "context->pos: " INT64_FORMAT,
+				 curr_pos, context->pos);
+			PG_RETURN_NULL();
+		}
+	}
+
+	/*
+	 * Evaluate PATTERN until one of expressions is not true or out of frame.
+	 */
+	relpos = 0;
+
+	forboth(lc, winstate->patternVariableList, lc1, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc));
+		char	*quantifier = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "relpos: %d pattern vname: %s quantifier: %s", relpos, vname, quantifier);
+
+		/* evaluate row pattern against current row */
+		relpos = evaluate_pattern(winobj, winstate, relpos, vname, quantifier, &expression_result);
+
+		/*
+		 * If the expression did not match, we are done.
+		 */
+		if (!expression_result)
+			break;
+
+		/* out of frame? */
+		if (relpos < 0)
+			break;
+
+		/* count up relative row position */
+		relpos++;
+	}
+
+	elog(DEBUG1, "relpos: %d", relpos);
+
+	/*
+	 * If current row satified the pattern, return argument expression.
+	 */
+	if (expression_result)
+	{
+		result = WinGetFuncArgInFrame(winobj, 0,
+									  0, WINDOW_SEEK_HEAD, false,
+									  &isnull, NULL);
+	}
+
+	/*
+	 * At this point we can set mark down to current pos -2.
+	 */
+	markpos = curr_pos -2;
+	elog(DEBUG1, "markpos: " INT64_FORMAT, markpos);
+	if (markpos >= 0)
+		WinSetMarkPosition(winobj, markpos);
+
+	if (expression_result)
+	{
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW)
+		{
+			context->pos += relpos;
+			elog(DEBUG1, "context->pos: " INT64_FORMAT, context->pos);
+		}
+		PG_RETURN_DATUM(result);
+	}
+
+	PG_RETURN_NULL();
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match relative row position
+ * -1: current row is out of frame
+ */
+static
+int evaluate_pattern(WindowObject winobj, WindowAggState *winstate,
+					 int relpos, char *vname, char *quantifier, bool *result)
+{
+	ExprContext	*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell	*lc1, *lc2;
+	ExprState	*pat;
+	Datum		eval_result;
+	int			sts;
+	bool		out_of_frame = false;
+	bool		isnull;
+	StringInfo	encoded_str = makeStringInfo();
+	char		pattern_str[128];
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+		bool	second_try_match = false;
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		for (;;)
+		{
+			if (!get_slots(winobj, winstate, relpos))
+			{
+				out_of_frame = true;
+				break;	/* current row is out of frame */
+			}
+
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: %d", vname, relpos);
+				break;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: %d", vname, relpos);
+					break;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: %d", vname, relpos);
+					appendStringInfoChar(encoded_str, vname[0]);
+
+					/* If quantifier is "+", we need to look for more matching row */
+					if (quantifier && !strcmp(quantifier, "+"))
+					{
+						/* remember that we want to try another row */
+						second_try_match = true;
+						relpos++;
+					}
+					else
+						break;
+				}
+			}
+		}
+		if (second_try_match)
+			relpos--;
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+
+		/* build regular expression */
+		snprintf(pattern_str, sizeof(pattern_str), "%c%s", vname[0], quantifier);
+
+		/*
+		 * Do regular expression matching against sequence of rows satisfying
+		 * the expression using regexp_instr().
+		 */
+		sts = DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													PointerGetDatum(cstring_to_text(encoded_str->data)),
+													PointerGetDatum(cstring_to_text(pattern_str))));
+		elog(DEBUG1, "regexp_instr returned: %d. str: %s regexp: %s",
+			 sts, encoded_str->data, pattern_str);
+		*result = (sts > 0)? true : false;
+	}
+	return relpos;
+}
+
+/*
+ * Get current, previous and next tuple.
+ * Returns true if still within frame.
+ */
+static bool
+get_slots(WindowObject winobj, WindowAggState *winstate, int current_pos)
+{
+	TupleTableSlot *slot;
+	bool	isnull, isout;
+	int		sts;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* for current row */
+	slot = winstate->temp_slot_1;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "current row is out of frame");
+		econtext->ecxt_scantuple = winstate->null_slot;
+		return false;
+	}
+	else
+		econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		sts = WinGetSlotInFrame(winobj, slot,
+								current_pos - 1, WINDOW_SEEK_HEAD, false,
+								&isnull, &isout);
+		if (sts < 0)
+		{
+			elog(DEBUG1, "previous row out of frame at: %d", current_pos);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		econtext->ecxt_outertuple = slot;
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	sts = WinGetSlotInFrame(winobj, slot,
+							current_pos + 1, WINDOW_SEEK_HEAD, false,
+							&isnull, &isout);
+	if (sts < 0)
+	{
+		elog(DEBUG1, "next row out of frame at: %d", current_pos);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+		econtext->ecxt_innertuple = slot;
+
+	return true;
+}
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..e3a9e0ffeb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,15 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'row pattern recognition in window',
+  proname => 'rpr', prokind => 'w', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_rpr' },
+{ oid => '6123', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6124', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..1643eaa6f1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,14 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2563,11 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..a0facf38fe 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,16 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Mon_Jun_26_17_45_07_2023_724)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v2-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v3 4/7] Row pattern recognition patch (executor).
@ 2023-07-26 10:49 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-07-26 10:49 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 701 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  18 +
 src/include/windowapi.h              |   8 +
 5 files changed, 758 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..0586bf57d6 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +192,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +206,19 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +694,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +814,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +886,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +946,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +961,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +981,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1050,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2129,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2466,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2567,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2761,69 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (nargs != 1)
+				continue;
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				continue;
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2831,77 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			elog(DEBUG1, "walker: varattno: %d varattnosyn: %d",attno, attnosyn);
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2919,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2970,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3312,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3100,7 +3332,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3652,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3766,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3843,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3867,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3904,400 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+
+		appendStringInfoChar(pattern_str, vname[0]);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s quantifier: %s", vname, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, vname[0]);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_outertuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_outertuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..fa100b2665 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..4fd3bd1a93 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,14 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2563,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..1e292648e9 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,15 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Wed_Jul_26_21_21_34_2023_317)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v3-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v3 4/7] Row pattern recognition patch (executor).
@ 2023-07-26 10:49 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-07-26 10:49 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 701 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  18 +
 src/include/windowapi.h              |   8 +
 5 files changed, 758 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..0586bf57d6 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +192,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +206,19 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +694,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +814,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +886,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +946,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +961,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +981,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1050,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2129,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2466,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2567,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2761,69 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (nargs != 1)
+				continue;
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				continue;
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2831,77 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			elog(DEBUG1, "walker: varattno: %d varattnosyn: %d",attno, attnosyn);
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2919,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2970,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3312,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3100,7 +3332,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3652,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3766,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3843,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3867,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3904,400 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+
+		appendStringInfoChar(pattern_str, vname[0]);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s quantifier: %s", vname, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, vname[0]);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_outertuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_outertuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..fa100b2665 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..4fd3bd1a93 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,14 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2563,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..1e292648e9 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,15 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Wed_Jul_26_21_21_34_2023_317)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v3-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v3 4/7] Row pattern recognition patch (executor).
@ 2023-07-26 10:49 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-07-26 10:49 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 701 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  18 +
 src/include/windowapi.h              |   8 +
 5 files changed, 758 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..0586bf57d6 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +192,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +206,19 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +694,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +814,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +886,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +946,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +961,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +981,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1050,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2129,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2466,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2567,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2761,69 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (nargs != 1)
+				continue;
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				continue;
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2831,77 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			elog(DEBUG1, "walker: varattno: %d varattnosyn: %d",attno, attnosyn);
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2919,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2970,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3312,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3100,7 +3332,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3652,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3766,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3843,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3867,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3904,400 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+
+		appendStringInfoChar(pattern_str, vname[0]);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s quantifier: %s", vname, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, vname[0]);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_outertuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_outertuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..fa100b2665 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..4fd3bd1a93 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,14 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2563,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..1e292648e9 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,15 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Wed_Jul_26_21_21_34_2023_317)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v3-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v3 4/7] Row pattern recognition patch (executor).
@ 2023-07-26 10:49 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-07-26 10:49 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 701 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  18 +
 src/include/windowapi.h              |   8 +
 5 files changed, 758 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..0586bf57d6 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,14 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Map between Var attno in a target list and the parsed attno.
+ */
+typedef struct AttnoMap {
+	List		*attno;			/* att number in target list (list of AttNumber) */
+	List		*attnosyn;		/* parsed att number (list of AttNumber) */
+} AttnoMap;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +192,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +206,19 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node, AttnoMap *map);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +694,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +814,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +886,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +946,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +961,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +981,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1050,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2129,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2466,12 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+	Var			*var;
+	int			nargs;
+	AttnoMap	attnomap;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2567,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2761,69 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+
+	/*
+	 * Collect mapping between varattno and varattnosyn in the targetlist.
+	 * XXX: For now we only check RPR's argument. Eventually we have to
+	 * recurse the targetlist to find out all mappings in Var nodes.
+	 */
+	attnomap.attno = NIL;
+	attnomap.attnosyn = NIL;
+
+	foreach (l, node->plan.targetlist)
+	{
+		te = lfirst(l);
+		if (IsA(te->expr, WindowFunc))
+		{
+			WindowFunc	*func = (WindowFunc *)te->expr;
+
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (nargs != 1)
+				continue;
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				continue;
+
+			var = (Var *)expr;
+			elog(DEBUG1, "resname: %s varattno: %d varattnosyn: %d",
+				 te->resname, var->varattno, var->varattnosyn);
+			attnomap.attno = lappend_int(attnomap.attno, var->varattno);
+			attnomap.attnosyn = lappend_int(attnomap.attnosyn, var->varattnosyn);
+		}
+	}
+
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			/* tweak expr so that it referes to outer slot */
+			attno_map((Node *)expr, &attnomap);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2831,77 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite Var node's varattno to the varattno which is used in the target
+ * list using AttnoMap.  We also rewrite varno so that it sees outer tuple
+ * (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node, AttnoMap *map)
+{
+	(void) expression_tree_walker(node, attno_map_walker, (void *) map);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	AttnoMap	*attnomap;
+	ListCell	*lc1, *lc2;
+	
+	if (node == NULL)
+		return false;
+
+	attnomap = (AttnoMap *) context;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				var->varno = OUTER_VAR;
+			else
+				var->varno = INNER_VAR;
+		}
+		return expression_tree_walker(node, attno_map_walker, (void *) context);
+	}
+	else if (IsA(node, Var))
+	{
+		var = (Var *)node;	 
+
+		elog(DEBUG1, "original varno: %d varattno: %d", var->varno, var->varattno);
+
+		forboth(lc1, attnomap->attno, lc2, attnomap->attnosyn)
+		{
+			int	attno = lfirst_int(lc1);
+			int	attnosyn = lfirst_int(lc2);
+
+			elog(DEBUG1, "walker: varattno: %d varattnosyn: %d",attno, attnosyn);
+			if (var->varattno == attnosyn)
+			{
+				elog(DEBUG1, "loc: %d rewrite varattno from: %d to %d", var->location, attnosyn, attno);
+				var->varattno = attno;
+			}
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, (void *) context);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2919,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2970,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3312,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3100,7 +3332,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3652,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3766,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3843,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3867,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3904,400 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+
+		appendStringInfoChar(pattern_str, vname[0]);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s quantifier: %s", vname, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList)
+	{
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, vname[0]);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_scantuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_outertuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_outertuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_outertuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_outertuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..fa100b2665 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..4fd3bd1a93 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,14 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2563,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..1e292648e9 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,15 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Wed_Jul_26_21_21_34_2023_317)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v3-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v4 4/7] Row pattern recognition patch (executor).
@ 2023-08-09 07:56 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-08-09 07:56 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 674 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 src/include/windowapi.h              |   8 +
 5 files changed, 732 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..5354e47045 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,20 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +687,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +807,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +879,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +939,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +954,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +974,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1043,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2122,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2459,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2557,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2751,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2791,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2859,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2910,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3252,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3100,7 +3272,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3592,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3706,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3783,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3807,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3844,433 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..fa100b2665 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..1e292648e9 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,15 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Wed_Aug__9_17_41_12_2023_134)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v4-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v4 4/7] Row pattern recognition patch (executor).
@ 2023-08-09 07:56 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-08-09 07:56 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 674 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 src/include/windowapi.h              |   8 +
 5 files changed, 732 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..5354e47045 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,20 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +687,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +807,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +879,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +939,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +954,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +974,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1043,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2122,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2459,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2557,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2751,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2791,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2859,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2910,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3252,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3100,7 +3272,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3592,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3706,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3783,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3807,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3844,433 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..fa100b2665 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..1e292648e9 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,15 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Wed_Aug__9_17_41_12_2023_134)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v4-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v4 4/7] Row pattern recognition patch (executor).
@ 2023-08-09 07:56 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-08-09 07:56 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 674 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 src/include/windowapi.h              |   8 +
 5 files changed, 732 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..5354e47045 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,20 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +687,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +807,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +879,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +939,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +954,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +974,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1043,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2122,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2459,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2557,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2751,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2791,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2859,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2910,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3252,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3100,7 +3272,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3592,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3706,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3783,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3807,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3844,433 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..fa100b2665 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..1e292648e9 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,15 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Wed_Aug__9_17_41_12_2023_134)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v4-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v4 4/7] Row pattern recognition patch (executor).
@ 2023-08-09 07:56 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-08-09 07:56 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 674 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 src/include/windowapi.h              |   8 +
 5 files changed, 732 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..5354e47045 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,20 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +687,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +807,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +879,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +939,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +954,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +974,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1043,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2122,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2459,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2557,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2751,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2791,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2859,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2910,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3252,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3100,7 +3272,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3592,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3706,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3783,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3807,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3844,433 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..fa100b2665 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..1e292648e9 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,15 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Wed_Aug__9_17_41_12_2023_134)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v4-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v4 4/7] Row pattern recognition patch (executor).
@ 2023-08-09 07:56 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-08-09 07:56 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 674 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 src/include/windowapi.h              |   8 +
 5 files changed, 732 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..5354e47045 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,20 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +687,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +807,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +879,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +939,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +954,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +974,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1043,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2122,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2459,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2557,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2751,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2791,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2859,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2910,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3252,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3100,7 +3272,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3592,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3706,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3783,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3807,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3844,433 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..fa100b2665 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..1e292648e9 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,15 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Wed_Aug__9_17_41_12_2023_134)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v4-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v4 4/7] Row pattern recognition patch (executor).
@ 2023-08-09 07:56 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-08-09 07:56 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 674 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 src/include/windowapi.h              |   8 +
 5 files changed, 732 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..5354e47045 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,20 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +687,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +807,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +879,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +939,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +954,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +974,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1043,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2122,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2459,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2557,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2751,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2791,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2859,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2910,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3080,7 +3252,7 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
  *
  * Returns true if successful, false if no such row
  */
-static bool
+bool
 window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 {
 	WindowAggState *winstate = winobj->winstate;
@@ -3100,7 +3272,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3592,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3706,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3783,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3807,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3844,433 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+WindowAggState *
+WinGetAggState(WindowObject winobj)
+{
+	return winobj->winstate;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6996073989..fa100b2665 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10397,6 +10397,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
diff --git a/src/include/windowapi.h b/src/include/windowapi.h
index b8c2c565d1..1e292648e9 100644
--- a/src/include/windowapi.h
+++ b/src/include/windowapi.h
@@ -58,7 +58,15 @@ extern Datum WinGetFuncArgInFrame(WindowObject winobj, int argno,
 								  int relpos, int seektype, bool set_mark,
 								  bool *isnull, bool *isout);
 
+extern int WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							 int relpos, int seektype, bool set_mark,
+							 bool *isnull, bool *isout);
+
 extern Datum WinGetFuncArgCurrent(WindowObject winobj, int argno,
 								  bool *isnull);
 
+extern WindowAggState *WinGetAggState(WindowObject winobj);
+
+extern bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
 #endif							/* WINDOWAPI_H */
-- 
2.25.1


----Next_Part(Wed_Aug__9_17_41_12_2023_134)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v4-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v5 4/7] Row pattern recognition patch (executor).
@ 2023-09-02 06:32 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-02 06:32 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 671 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 4 files changed, 722 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..2e59369a71 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,25 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +692,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +812,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +884,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +944,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +959,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +979,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1048,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2127,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2464,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2562,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2756,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2796,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2864,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2915,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3277,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3597,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3711,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3788,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3812,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3849,427 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_Sep__2_15_52_35_2023_273)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v5-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v5 4/7] Row pattern recognition patch (executor).
@ 2023-09-02 06:32 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-02 06:32 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 671 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 4 files changed, 722 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..2e59369a71 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,25 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +692,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +812,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +884,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +944,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +959,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +979,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1048,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2127,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2464,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2562,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2756,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2796,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2864,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2915,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3277,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3597,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3711,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3788,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3812,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3849,427 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_Sep__2_15_52_35_2023_273)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v5-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v5 4/7] Row pattern recognition patch (executor).
@ 2023-09-02 06:32 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-02 06:32 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 671 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 4 files changed, 722 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..2e59369a71 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,25 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +692,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +812,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +884,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +944,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +959,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +979,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1048,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2127,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2464,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2562,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2756,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2796,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2864,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2915,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3277,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3597,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3711,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3788,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3812,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3849,427 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_Sep__2_15_52_35_2023_273)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v5-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v5 4/7] Row pattern recognition patch (executor).
@ 2023-09-02 06:32 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-02 06:32 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 671 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 4 files changed, 722 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..2e59369a71 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,25 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +692,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +812,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +884,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +944,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +959,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +979,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1048,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2127,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2464,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2562,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2756,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2796,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2864,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2915,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3277,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3597,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3711,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3788,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3812,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3849,427 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_Sep__2_15_52_35_2023_273)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v5-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v5 4/7] Row pattern recognition patch (executor).
@ 2023-09-02 06:32 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-02 06:32 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 671 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 4 files changed, 722 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..2e59369a71 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,25 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +692,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +812,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +884,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +944,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +959,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +979,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1048,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2127,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2464,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2562,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2756,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2796,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2864,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2915,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3277,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3597,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3711,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3788,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3812,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3849,427 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_Sep__2_15_52_35_2023_273)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v5-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v5 4/7] Row pattern recognition patch (executor).
@ 2023-09-02 06:32 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-02 06:32 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 671 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  38 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  19 +
 4 files changed, 722 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..2e59369a71 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,25 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +692,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		reduced_frame_set;
+	bool		check_reduced_frame;
+	int			num_rows_in_reduced_frame;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -790,6 +812,7 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
 			winstate->aggregatedupto <= winstate->frameheadpos)
 		{
+			elog(DEBUG1, "peraggstate->restart  is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +884,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -919,6 +944,10 @@ eval_windowaggregates(WindowAggState *winstate)
 		ExecClearTuple(agg_row_slot);
 	}
 
+	reduced_frame_set = false;
+	check_reduced_frame = false;
+	num_rows_in_reduced_frame = 0;
+
 	/*
 	 * Advance until we reach a row not in frame (or end of partition).
 	 *
@@ -930,12 +959,18 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
 			if (!window_gettupleslot(agg_winobj, winstate->aggregatedupto,
 									 agg_row_slot))
+			{
+				if (check_reduced_frame)
+					winstate->aggregatedupto--;
 				break;			/* must be end of partition */
+			}
 		}
 
 		/*
@@ -944,10 +979,47 @@ eval_windowaggregates(WindowAggState *winstate)
 		 */
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
+		{
+			if (winstate->patternVariableList != NIL && check_reduced_frame)
+				winstate->aggregatedupto--;
 			break;
+		}
 		if (ret == 0)
 			goto next_tuple;
 
+		if (winstate->patternVariableList != NIL)
+		{
+			if (!reduced_frame_set)
+			{
+				num_rows_in_reduced_frame = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+				reduced_frame_set = true;
+				elog(DEBUG1, "set num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+
+				if (num_rows_in_reduced_frame <= 0)
+					break;
+
+				else if (num_rows_in_reduced_frame > 0)
+					check_reduced_frame = true;
+			}
+
+			if (check_reduced_frame)
+			{
+				elog(DEBUG1, "decrease num_rows_in_reduced_frame: %d pos: " INT64_FORMAT,
+					 num_rows_in_reduced_frame, winstate->aggregatedupto);
+				num_rows_in_reduced_frame--;
+				if (num_rows_in_reduced_frame < 0)
+				{
+					/*
+					 * No more rows remain in the reduced frame. Finish
+					 * accumulating row into the aggregates.
+					 */
+					winstate->aggregatedupto--;
+					break;
+				}
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1048,8 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+	elog(DEBUG1, "===== break loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -2053,6 +2127,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2388,6 +2464,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2562,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2756,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2796,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+	
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2864,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2915,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3277,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3597,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,6 +3711,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
@@ -3565,6 +3788,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3812,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3849,427 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	if (winstate->patternVariableList == NIL)
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		return 0;
+	}
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check whether the row speicied by pos is in the reduced frame. The
+	 * second and subsequent rows need to be recognized as "unmatched" rows if
+	 * AFTER MATCH SKIP PAST LAST ROW is defined.
+	 */
+	if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+		pos > winstate->headpos_in_reduced_frame &&
+		pos < (winstate->headpos_in_reduced_frame + winstate->num_rows_in_reduced_frame))
+		return -2;
+		
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no matches found in the first row */
+		return -1;
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG1, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	if (num_matched_rows <= 0)
+		return -1;
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	winstate->headpos_in_reduced_frame = original_pos;
+	winstate->num_rows_in_reduced_frame = num_matched_rows;
+
+	return num_matched_rows;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos, 
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		elog(DEBUG1, "evaluate_pattern: define variable: %s, pattern variable: %s", name, vname);
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..e4cab36ec9 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,26 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..2bd6fcb5e1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2519,6 +2519,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */	
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2564,16 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/* head of the reduced window frame */
+	int64		headpos_in_reduced_frame;
+	/* number of rows in the reduced window frame */
+	int64		num_rows_in_reduced_frame;
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_Sep__2_15_52_35_2023_273)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v5-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v6 4/7] Row pattern recognition patch (executor).
@ 2023-09-12 05:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-12 05:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 842 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 898 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..32270d051a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row or an unmatched row, we don't need to
+		 * accumulate rows, just return NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			(get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED ||
+			 get_reduced_frame_map(winstate, winstate->currentpos) == RF_UNMATCHED))
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is enabled and we just return NULL. because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row or unmatched row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,561 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Tue_Sep_12_15_18_43_2023_359)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v6-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v6 4/7] Row pattern recognition patch (executor).
@ 2023-09-12 05:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-12 05:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 842 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 898 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..32270d051a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row or an unmatched row, we don't need to
+		 * accumulate rows, just return NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			(get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED ||
+			 get_reduced_frame_map(winstate, winstate->currentpos) == RF_UNMATCHED))
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is enabled and we just return NULL. because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row or unmatched row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,561 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Tue_Sep_12_15_18_43_2023_359)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v6-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v6 4/7] Row pattern recognition patch (executor).
@ 2023-09-12 05:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-12 05:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 842 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 898 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..32270d051a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row or an unmatched row, we don't need to
+		 * accumulate rows, just return NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			(get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED ||
+			 get_reduced_frame_map(winstate, winstate->currentpos) == RF_UNMATCHED))
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is enabled and we just return NULL. because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row or unmatched row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,561 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Tue_Sep_12_15_18_43_2023_359)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v6-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v6 4/7] Row pattern recognition patch (executor).
@ 2023-09-12 05:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-12 05:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 842 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 898 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..32270d051a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row or an unmatched row, we don't need to
+		 * accumulate rows, just return NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			(get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED ||
+			 get_reduced_frame_map(winstate, winstate->currentpos) == RF_UNMATCHED))
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is enabled and we just return NULL. because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row or unmatched row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,561 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Tue_Sep_12_15_18_43_2023_359)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v6-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v6 4/7] Row pattern recognition patch (executor).
@ 2023-09-12 05:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-12 05:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 842 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 898 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..32270d051a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row or an unmatched row, we don't need to
+		 * accumulate rows, just return NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			(get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED ||
+			 get_reduced_frame_map(winstate, winstate->currentpos) == RF_UNMATCHED))
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is enabled and we just return NULL. because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row or unmatched row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,561 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Tue_Sep_12_15_18_43_2023_359)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v6-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v6 4/7] Row pattern recognition patch (executor).
@ 2023-09-12 05:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-12 05:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 842 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 898 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..32270d051a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row or an unmatched row, we don't need to
+		 * accumulate rows, just return NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			(get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED ||
+			 get_reduced_frame_map(winstate, winstate->currentpos) == RF_UNMATCHED))
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is enabled and we just return NULL. because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row or unmatched row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,561 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Tue_Sep_12_15_18_43_2023_359)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v6-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v6 4/7] Row pattern recognition patch (executor).
@ 2023-09-12 05:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-12 05:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 842 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 898 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..32270d051a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row or an unmatched row, we don't need to
+		 * accumulate rows, just return NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			(get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED ||
+			 get_reduced_frame_map(winstate, winstate->currentpos) == RF_UNMATCHED))
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is enabled and we just return NULL. because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row or unmatched row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,561 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search set of encode_str.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, char *encoded_str, int *resultlen)
+{
+	char	*p;
+
+	if (set_index >= set_size)
+	{
+		Datum	d;
+		text	*res;
+		char	*substr;
+
+		/*
+		 * We first perform pattern matching using regexp_instr, then call
+		 * textregexsubstr to get matched substring to know how log the
+		 * matched string is. That is the number of rows in the reduced window
+		 * frame.  The reason why we can't call textregexsubstr is, it error
+		 * out if pattern is not match.
+		 */
+		if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+												  PointerGetDatum(cstring_to_text(encoded_str)),
+												  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+		{
+			d = DirectFunctionCall2Coll(textregexsubstr,
+										DEFAULT_COLLATION_OID,
+										PointerGetDatum(cstring_to_text(encoded_str)),
+										PointerGetDatum(cstring_to_text(pattern)));
+			if (d != 0)
+			{
+				int		len;
+
+				res = DatumGetTextPP(d);
+				substr = text_to_cstring(res);
+				len = strlen(substr);
+				if (len > *resultlen)
+					/* remember the longest match */
+					*resultlen = len;
+			}
+		}
+		return;
+	}
+
+	p = str_set[set_index]->data;
+	while (*p)
+	{
+		encoded_str[set_index] = *p;
+		p++;
+		search_str_set_recurse(pattern, str_set, set_size, set_index + 1, encoded_str, resultlen);
+	}
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Tue_Sep_12_15_18_43_2023_359)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v6-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v7 4/7] Row pattern recognition patch (executor).
@ 2023-09-22 04:53 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-22 04:53 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 853 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 909 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..84d1b8acaa 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   int str_index, char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,572 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform regex search pattern against encoded string array str_set.
+ * returns the number of longest matching rows.
+ * str_set: array of encoded string. Each array element corresponds to each
+ * row.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, 0,
+						   encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ *
+ * Recurse among matched pattern variables in a row.  The max recursion depth
+ * is number of pattern variables matched in a row.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, int str_index,
+							char *encoded_str, int *resultlen)
+{
+	for (;;)
+	{
+		char	c;
+
+		c = str_set[set_index]->data[str_index];
+		if (c == '\0')
+			return;
+		encoded_str[set_index] = c;
+		set_index++;
+
+		if (set_index >= set_size)
+		{
+			Datum	d;
+			text	*res;
+			char	*substr;
+
+			/*
+			 * We first perform pattern matching using regexp_instr, then call
+			 * textregexsubstr to get matched substring to know how long the
+			 * matched string is. That is the number of rows in the reduced window
+			 * frame.  The reason why we can't call textregexsubstr in the first
+			 * place is, it errors out if pattern does not match.
+			 */
+			if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													  PointerGetDatum(cstring_to_text(encoded_str)),
+													  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+			{
+				d = DirectFunctionCall2Coll(textregexsubstr,
+											DEFAULT_COLLATION_OID,
+											PointerGetDatum(cstring_to_text(encoded_str)),
+											PointerGetDatum(cstring_to_text(pattern)));
+				if (d != 0)
+				{
+					int		len;
+
+					res = DatumGetTextPP(d);
+					substr = text_to_cstring(res);
+					len = strlen(substr);
+					if (len > *resultlen)
+						/* remember the longest match */
+						*resultlen = len;
+				}
+			}
+			return;
+		}
+		else
+			search_str_set_recurse(pattern, str_set, set_size, set_index, str_index + 1,
+								   encoded_str, resultlen);
+	}
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Sep_22_14_16_40_2023_530)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v7-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v7 4/7] Row pattern recognition patch (executor).
@ 2023-09-22 04:53 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-22 04:53 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 853 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 909 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..84d1b8acaa 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   int str_index, char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,572 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform regex search pattern against encoded string array str_set.
+ * returns the number of longest matching rows.
+ * str_set: array of encoded string. Each array element corresponds to each
+ * row.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, 0,
+						   encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ *
+ * Recurse among matched pattern variables in a row.  The max recursion depth
+ * is number of pattern variables matched in a row.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, int str_index,
+							char *encoded_str, int *resultlen)
+{
+	for (;;)
+	{
+		char	c;
+
+		c = str_set[set_index]->data[str_index];
+		if (c == '\0')
+			return;
+		encoded_str[set_index] = c;
+		set_index++;
+
+		if (set_index >= set_size)
+		{
+			Datum	d;
+			text	*res;
+			char	*substr;
+
+			/*
+			 * We first perform pattern matching using regexp_instr, then call
+			 * textregexsubstr to get matched substring to know how long the
+			 * matched string is. That is the number of rows in the reduced window
+			 * frame.  The reason why we can't call textregexsubstr in the first
+			 * place is, it errors out if pattern does not match.
+			 */
+			if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													  PointerGetDatum(cstring_to_text(encoded_str)),
+													  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+			{
+				d = DirectFunctionCall2Coll(textregexsubstr,
+											DEFAULT_COLLATION_OID,
+											PointerGetDatum(cstring_to_text(encoded_str)),
+											PointerGetDatum(cstring_to_text(pattern)));
+				if (d != 0)
+				{
+					int		len;
+
+					res = DatumGetTextPP(d);
+					substr = text_to_cstring(res);
+					len = strlen(substr);
+					if (len > *resultlen)
+						/* remember the longest match */
+						*resultlen = len;
+				}
+			}
+			return;
+		}
+		else
+			search_str_set_recurse(pattern, str_set, set_size, set_index, str_index + 1,
+								   encoded_str, resultlen);
+	}
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Sep_22_14_16_40_2023_530)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v7-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v7 4/7] Row pattern recognition patch (executor).
@ 2023-09-22 04:53 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-22 04:53 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 853 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 909 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..84d1b8acaa 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   int str_index, char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,572 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform regex search pattern against encoded string array str_set.
+ * returns the number of longest matching rows.
+ * str_set: array of encoded string. Each array element corresponds to each
+ * row.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, 0,
+						   encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ *
+ * Recurse among matched pattern variables in a row.  The max recursion depth
+ * is number of pattern variables matched in a row.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, int str_index,
+							char *encoded_str, int *resultlen)
+{
+	for (;;)
+	{
+		char	c;
+
+		c = str_set[set_index]->data[str_index];
+		if (c == '\0')
+			return;
+		encoded_str[set_index] = c;
+		set_index++;
+
+		if (set_index >= set_size)
+		{
+			Datum	d;
+			text	*res;
+			char	*substr;
+
+			/*
+			 * We first perform pattern matching using regexp_instr, then call
+			 * textregexsubstr to get matched substring to know how long the
+			 * matched string is. That is the number of rows in the reduced window
+			 * frame.  The reason why we can't call textregexsubstr in the first
+			 * place is, it errors out if pattern does not match.
+			 */
+			if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													  PointerGetDatum(cstring_to_text(encoded_str)),
+													  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+			{
+				d = DirectFunctionCall2Coll(textregexsubstr,
+											DEFAULT_COLLATION_OID,
+											PointerGetDatum(cstring_to_text(encoded_str)),
+											PointerGetDatum(cstring_to_text(pattern)));
+				if (d != 0)
+				{
+					int		len;
+
+					res = DatumGetTextPP(d);
+					substr = text_to_cstring(res);
+					len = strlen(substr);
+					if (len > *resultlen)
+						/* remember the longest match */
+						*resultlen = len;
+				}
+			}
+			return;
+		}
+		else
+			search_str_set_recurse(pattern, str_set, set_size, set_index, str_index + 1,
+								   encoded_str, resultlen);
+	}
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Sep_22_14_16_40_2023_530)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v7-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v7 4/7] Row pattern recognition patch (executor).
@ 2023-09-22 04:53 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-22 04:53 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 853 ++++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |  37 +-
 src/include/catalog/pg_proc.dat      |   6 +
 src/include/nodes/execnodes.h        |  26 +
 4 files changed, 909 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..84d1b8acaa 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -182,8 +184,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +198,32 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringInfo *str_set, int set_size);
+static void search_str_set_recurse(char *pattern, StringInfo *str_set, int set_size, int set_index,
+								   int str_index, char *encoded_str, int *resultlen);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +699,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +805,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +818,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +894,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +952,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +988,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1008,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1058,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1079,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1183,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2147,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2317,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2495,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2593,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2787,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2827,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2895,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2946,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3308,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3628,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3742,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3823,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3847,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3884,572 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+
+	/*
+	 * Array of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+	#define ENCODED_STR_ARRAY_ALLOC_SIZE	128
+	StringInfo	*str_set = NULL;
+	int		str_set_index;
+	int		str_set_size;
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		/* build encoded string array */
+		if (str_set == NULL)
+		{
+			str_set_index = 0;
+			str_set_size = ENCODED_STR_ARRAY_ALLOC_SIZE * sizeof(StringInfo);
+			str_set = palloc(str_set_size);
+		}
+
+		str_set[str_set_index++] = encoded_str;
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		if (str_set_index >= str_set_size)
+		{
+			str_set_size *= 2;
+			str_set = repalloc(str_set, str_set_size);
+		}
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (str_set == NULL)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set, str_set_index);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform regex search pattern against encoded string array str_set.
+ * returns the number of longest matching rows.
+ * str_set: array of encoded string. Each array element corresponds to each
+ * row.
+ * set_size: size of set_str array.
+ */
+static
+int search_str_set(char *pattern, StringInfo *str_set, int set_size)
+{
+	char		*encoded_str = palloc0(set_size+1);
+	int			resultlen = 0;
+
+	search_str_set_recurse(pattern, str_set, set_size, 0, 0,
+						   encoded_str, &resultlen);
+	elog(DEBUG1, "search_str_set returns %d", resultlen);
+	return resultlen;
+}
+
+/*
+ * Workhorse of search_str_set.
+ *
+ * Recurse among matched pattern variables in a row.  The max recursion depth
+ * is number of pattern variables matched in a row.
+ */
+static
+void search_str_set_recurse(char *pattern, StringInfo *str_set,
+							int set_size, int set_index, int str_index,
+							char *encoded_str, int *resultlen)
+{
+	for (;;)
+	{
+		char	c;
+
+		c = str_set[set_index]->data[str_index];
+		if (c == '\0')
+			return;
+		encoded_str[set_index] = c;
+		set_index++;
+
+		if (set_index >= set_size)
+		{
+			Datum	d;
+			text	*res;
+			char	*substr;
+
+			/*
+			 * We first perform pattern matching using regexp_instr, then call
+			 * textregexsubstr to get matched substring to know how long the
+			 * matched string is. That is the number of rows in the reduced window
+			 * frame.  The reason why we can't call textregexsubstr in the first
+			 * place is, it errors out if pattern does not match.
+			 */
+			if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+													  PointerGetDatum(cstring_to_text(encoded_str)),
+													  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+			{
+				d = DirectFunctionCall2Coll(textregexsubstr,
+											DEFAULT_COLLATION_OID,
+											PointerGetDatum(cstring_to_text(encoded_str)),
+											PointerGetDatum(cstring_to_text(pattern)));
+				if (d != 0)
+				{
+					int		len;
+
+					res = DatumGetTextPP(d);
+					substr = text_to_cstring(res);
+					len = strlen(substr);
+					if (len > *resultlen)
+						/* remember the longest match */
+						*resultlen = len;
+				}
+			}
+			return;
+		}
+		else
+			search_str_set_recurse(pattern, str_set, set_size, set_index, str_index + 1,
+								   encoded_str, resultlen);
+	}
+}
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Sep_22_14_16_40_2023_530)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v7-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v8 4/7] Row pattern recognition patch (executor).
@ 2023-09-25 05:01 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-25 05:01 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1024 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1080 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..5f2dfdf943 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,12 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +190,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +204,37 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +710,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +816,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +829,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +905,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +963,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +999,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1019,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1069,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1090,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1194,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2158,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2328,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2506,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2604,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2798,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2838,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2906,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2957,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3319,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3639,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3753,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3834,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3858,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3895,732 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+
+	/*
+	 * Set of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set.  str_set is a set
+ * of StringInfo. Each StringInfo has a string comprising initial characters
+ * of pattern variables being true in a row.  Returns the longest number of
+ * the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set)
+{
+	int			set_size;	/* number of rows in the set */
+	int			resultlen = 0;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * loop over each new pattern variable char in previous result
+			 * char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				p++;	/* next pattern variable */
+			}
+		}
+		else
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = str->data;
+
+				/*
+				 * loop over each pattern variable char in the input set.
+				 */
+				while (*p)
+				{
+					StringInfo	new = makeStringInfo();
+
+					/* copy source string */
+					appendStringInfoString(new, old->data);
+					/* add pattern variable char */
+					appendStringInfoChar(new, *p);
+					/* add new one to string set */
+					string_set_add(new_str_set, new);
+					p++;	/* next pattern variable */
+				}
+			}
+			/* we no long need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		int			len;
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no long need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(cstring_to_text(encoded_str)),
+											  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(cstring_to_text(encoded_str)),
+									PointerGetDatum(cstring_to_text(pattern)));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+		}
+	}
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Sep_25_14_26_30_2023_752)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v8-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v8 4/7] Row pattern recognition patch (executor).
@ 2023-09-25 05:01 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-25 05:01 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1024 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1080 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..5f2dfdf943 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,12 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +190,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +204,37 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +710,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +816,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +829,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +905,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +963,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +999,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1019,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1069,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1090,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1194,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2158,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2328,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2506,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2604,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2798,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2838,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2906,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2957,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3319,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3639,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3753,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3834,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3858,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3895,732 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+
+	/*
+	 * Set of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set.  str_set is a set
+ * of StringInfo. Each StringInfo has a string comprising initial characters
+ * of pattern variables being true in a row.  Returns the longest number of
+ * the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set)
+{
+	int			set_size;	/* number of rows in the set */
+	int			resultlen = 0;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * loop over each new pattern variable char in previous result
+			 * char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				p++;	/* next pattern variable */
+			}
+		}
+		else
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = str->data;
+
+				/*
+				 * loop over each pattern variable char in the input set.
+				 */
+				while (*p)
+				{
+					StringInfo	new = makeStringInfo();
+
+					/* copy source string */
+					appendStringInfoString(new, old->data);
+					/* add pattern variable char */
+					appendStringInfoChar(new, *p);
+					/* add new one to string set */
+					string_set_add(new_str_set, new);
+					p++;	/* next pattern variable */
+				}
+			}
+			/* we no long need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		int			len;
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no long need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(cstring_to_text(encoded_str)),
+											  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(cstring_to_text(encoded_str)),
+									PointerGetDatum(cstring_to_text(pattern)));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+		}
+	}
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Sep_25_14_26_30_2023_752)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v8-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v8 4/7] Row pattern recognition patch (executor).
@ 2023-09-25 05:01 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-25 05:01 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1024 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1080 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..5f2dfdf943 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,12 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +190,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +204,37 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +710,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +816,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +829,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +905,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +963,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +999,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1019,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1069,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1090,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1194,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2158,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2328,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2506,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2604,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2798,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2838,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2906,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2957,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3319,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3639,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3753,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3834,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3858,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3895,732 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+
+	/*
+	 * Set of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set.  str_set is a set
+ * of StringInfo. Each StringInfo has a string comprising initial characters
+ * of pattern variables being true in a row.  Returns the longest number of
+ * the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set)
+{
+	int			set_size;	/* number of rows in the set */
+	int			resultlen = 0;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * loop over each new pattern variable char in previous result
+			 * char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				p++;	/* next pattern variable */
+			}
+		}
+		else
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = str->data;
+
+				/*
+				 * loop over each pattern variable char in the input set.
+				 */
+				while (*p)
+				{
+					StringInfo	new = makeStringInfo();
+
+					/* copy source string */
+					appendStringInfoString(new, old->data);
+					/* add pattern variable char */
+					appendStringInfoChar(new, *p);
+					/* add new one to string set */
+					string_set_add(new_str_set, new);
+					p++;	/* next pattern variable */
+				}
+			}
+			/* we no long need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		int			len;
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no long need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(cstring_to_text(encoded_str)),
+											  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(cstring_to_text(encoded_str)),
+									PointerGetDatum(cstring_to_text(pattern)));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+		}
+	}
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Sep_25_14_26_30_2023_752)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v8-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v8 4/7] Row pattern recognition patch (executor).
@ 2023-09-25 05:01 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-09-25 05:01 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1024 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1080 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 310ac23e3a..5f2dfdf943 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,12 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +190,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +204,37 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +710,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +816,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +829,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +905,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +963,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +999,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1019,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1069,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1090,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1194,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2158,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2328,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2506,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2604,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2798,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2838,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2691,6 +2906,8 @@ ExecEndWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -2740,6 +2957,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3100,7 +3319,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3420,14 +3639,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3494,11 +3753,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3565,6 +3834,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3583,15 +3858,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3622,3 +3895,732 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+
+	/*
+	 * Set of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set.  str_set is a set
+ * of StringInfo. Each StringInfo has a string comprising initial characters
+ * of pattern variables being true in a row.  Returns the longest number of
+ * the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set)
+{
+	int			set_size;	/* number of rows in the set */
+	int			resultlen = 0;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * loop over each new pattern variable char in previous result
+			 * char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				p++;	/* next pattern variable */
+			}
+		}
+		else
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = str->data;
+
+				/*
+				 * loop over each pattern variable char in the input set.
+				 */
+				while (*p)
+				{
+					StringInfo	new = makeStringInfo();
+
+					/* copy source string */
+					appendStringInfoString(new, old->data);
+					/* add pattern variable char */
+					appendStringInfoChar(new, *p);
+					/* add new one to string set */
+					string_set_add(new_str_set, new);
+					p++;	/* next pattern variable */
+				}
+			}
+			/* we no long need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		int			len;
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no long need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(cstring_to_text(encoded_str)),
+											  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(cstring_to_text(encoded_str)),
+									PointerGetDatum(cstring_to_text(pattern)));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+		}
+	}
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+
+		ret = row_is_in_frame(winstate, current_pos, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+			return false;
+		}
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..d20f803cf5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cb714f4a19..63feb68f60 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2471,6 +2471,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2519,6 +2524,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2555,6 +2569,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Sep_25_14_26_30_2023_752)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v8-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v9 4/7] Row pattern recognition patch (executor).
@ 2023-10-04 05:51 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-10-04 05:51 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1021 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1077 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..5da1bb5a6f 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,12 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +190,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +204,37 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +710,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +816,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +829,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +905,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +963,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +999,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1019,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1069,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1090,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1194,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2158,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2328,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2506,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2604,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2798,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2838,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2938,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3300,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3620,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3734,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3815,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3839,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3876,731 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+
+	/*
+	 * Set of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set.  str_set is a set
+ * of StringInfo. Each StringInfo has a string comprising initial characters
+ * of pattern variables being true in a row.  Returns the longest number of
+ * the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set)
+{
+	int			set_size;	/* number of rows in the set */
+	int			resultlen = 0;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * loop over each new pattern variable char in previous result
+			 * char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				p++;	/* next pattern variable */
+			}
+		}
+		else
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = str->data;
+
+				/*
+				 * loop over each pattern variable char in the input set.
+				 */
+				while (*p)
+				{
+					StringInfo	new = makeStringInfo();
+
+					/* copy source string */
+					appendStringInfoString(new, old->data);
+					/* add pattern variable char */
+					appendStringInfoChar(new, *p);
+					/* add new one to string set */
+					string_set_add(new_str_set, new);
+					p++;	/* next pattern variable */
+				}
+			}
+			/* we no long need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		int			len;
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no long need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(cstring_to_text(encoded_str)),
+											  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(cstring_to_text(encoded_str)),
+									PointerGetDatum(cstring_to_text(pattern)));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+		}
+	}
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f0b7b9cbd8..24d288336a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 108d69ba28..28d098b1cf 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_Oct__4_15_03_28_2023_821)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v9-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v9 4/7] Row pattern recognition patch (executor).
@ 2023-10-04 05:51 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-10-04 05:51 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1021 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1077 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..5da1bb5a6f 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,12 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +190,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +204,37 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +710,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +816,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +829,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +905,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +963,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +999,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1019,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1069,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1090,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1194,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2158,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2328,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2506,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2604,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2798,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2838,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2938,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3300,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3620,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3734,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3815,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3839,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3876,731 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+
+	/*
+	 * Set of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set.  str_set is a set
+ * of StringInfo. Each StringInfo has a string comprising initial characters
+ * of pattern variables being true in a row.  Returns the longest number of
+ * the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set)
+{
+	int			set_size;	/* number of rows in the set */
+	int			resultlen = 0;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * loop over each new pattern variable char in previous result
+			 * char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				p++;	/* next pattern variable */
+			}
+		}
+		else
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = str->data;
+
+				/*
+				 * loop over each pattern variable char in the input set.
+				 */
+				while (*p)
+				{
+					StringInfo	new = makeStringInfo();
+
+					/* copy source string */
+					appendStringInfoString(new, old->data);
+					/* add pattern variable char */
+					appendStringInfoChar(new, *p);
+					/* add new one to string set */
+					string_set_add(new_str_set, new);
+					p++;	/* next pattern variable */
+				}
+			}
+			/* we no long need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		int			len;
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no long need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(cstring_to_text(encoded_str)),
+											  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(cstring_to_text(encoded_str)),
+									PointerGetDatum(cstring_to_text(pattern)));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+		}
+	}
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f0b7b9cbd8..24d288336a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 108d69ba28..28d098b1cf 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_Oct__4_15_03_28_2023_821)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v9-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v9 4/7] Row pattern recognition patch (executor).
@ 2023-10-04 05:51 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-10-04 05:51 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1021 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1077 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..5da1bb5a6f 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,12 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +190,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +204,37 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +710,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +816,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +829,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +905,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +963,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +999,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1019,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1069,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1090,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1194,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2158,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2328,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2506,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2604,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2798,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2838,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2938,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3300,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3620,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3734,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3815,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3839,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3876,731 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+
+	/*
+	 * Set of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set.  str_set is a set
+ * of StringInfo. Each StringInfo has a string comprising initial characters
+ * of pattern variables being true in a row.  Returns the longest number of
+ * the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set)
+{
+	int			set_size;	/* number of rows in the set */
+	int			resultlen = 0;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * loop over each new pattern variable char in previous result
+			 * char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				p++;	/* next pattern variable */
+			}
+		}
+		else
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = str->data;
+
+				/*
+				 * loop over each pattern variable char in the input set.
+				 */
+				while (*p)
+				{
+					StringInfo	new = makeStringInfo();
+
+					/* copy source string */
+					appendStringInfoString(new, old->data);
+					/* add pattern variable char */
+					appendStringInfoChar(new, *p);
+					/* add new one to string set */
+					string_set_add(new_str_set, new);
+					p++;	/* next pattern variable */
+				}
+			}
+			/* we no long need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		int			len;
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no long need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(cstring_to_text(encoded_str)),
+											  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(cstring_to_text(encoded_str)),
+									PointerGetDatum(cstring_to_text(pattern)));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+		}
+	}
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f0b7b9cbd8..24d288336a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 108d69ba28..28d098b1cf 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_Oct__4_15_03_28_2023_821)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v9-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v9 4/7] Row pattern recognition patch (executor).
@ 2023-10-04 05:51 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-10-04 05:51 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1021 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1077 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..5da1bb5a6f 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,12 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +190,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +204,37 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +710,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +816,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +829,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +905,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +963,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +999,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1019,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1069,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1090,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1194,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2158,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2328,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2506,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2604,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2798,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2838,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2938,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3300,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3620,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3734,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3815,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3839,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3876,731 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+
+	/*
+	 * Set of pattern variables evaluted to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+		elog(DEBUG1, "vname: %s initial: %c quantifier: %s", vname, initial, quantifier);
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	num_matched_rows = search_str_set(pattern_str->data, str_set);
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set.  str_set is a set
+ * of StringInfo. Each StringInfo has a string comprising initial characters
+ * of pattern variables being true in a row.  Returns the longest number of
+ * the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set)
+{
+	int			set_size;	/* number of rows in the set */
+	int			resultlen = 0;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * loop over each new pattern variable char in previous result
+			 * char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				p++;	/* next pattern variable */
+			}
+		}
+		else
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = str->data;
+
+				/*
+				 * loop over each pattern variable char in the input set.
+				 */
+				while (*p)
+				{
+					StringInfo	new = makeStringInfo();
+
+					/* copy source string */
+					appendStringInfoString(new, old->data);
+					/* add pattern variable char */
+					appendStringInfoChar(new, *p);
+					/* add new one to string set */
+					string_set_add(new_str_set, new);
+					p++;	/* next pattern variable */
+				}
+			}
+			/* we no long need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		int			len;
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no long need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(cstring_to_text(encoded_str)),
+											  PointerGetDatum(cstring_to_text(pattern)))) > 0)
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(cstring_to_text(encoded_str)),
+									PointerGetDatum(cstring_to_text(pattern)));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+		}
+	}
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f0b7b9cbd8..24d288336a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10416,6 +10416,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 108d69ba28..28d098b1cf 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_Oct__4_15_03_28_2023_821)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v9-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v10 4/7] Row pattern recognition patch (executor).
@ 2023-10-22 02:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-10-22 02:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1363 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1419 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..2e1baef7ea 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3. 
+ * 
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,43 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+//static int	search_str_set(char *pattern, StringSet *str_set, int *initial_orders);
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +744,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +850,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +863,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +939,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +997,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1033,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1053,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1103,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1124,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1228,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2192,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2362,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2540,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2638,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2832,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2872,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2972,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3334,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3654,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3768,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3849,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3873,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3910,1039 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+					new = makeStringInfo();
+					appendStringInfoString(new, old->data);
+					string_set_add(new_str_set, new);
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{						
+						/* copy source string */
+						new = makeStringInfo();
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c92d0631a0..ebb017b015 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10425,6 +10425,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 108d69ba28..28d098b1cf 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sun_Oct_22_11_39_20_2023_140)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v10-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v10 4/7] Row pattern recognition patch (executor).
@ 2023-10-22 02:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-10-22 02:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1363 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1419 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..2e1baef7ea 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3. 
+ * 
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,43 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+//static int	search_str_set(char *pattern, StringSet *str_set, int *initial_orders);
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +744,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +850,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +863,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +939,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +997,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1033,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1053,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1103,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1124,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1228,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2192,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2362,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2540,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2638,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2832,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2872,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2972,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3334,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3654,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3768,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3849,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3873,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3910,1039 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+					new = makeStringInfo();
+					appendStringInfoString(new, old->data);
+					string_set_add(new_str_set, new);
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{						
+						/* copy source string */
+						new = makeStringInfo();
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c92d0631a0..ebb017b015 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10425,6 +10425,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 108d69ba28..28d098b1cf 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sun_Oct_22_11_39_20_2023_140)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v10-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v10 4/7] Row pattern recognition patch (executor).
@ 2023-10-22 02:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-10-22 02:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1363 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1419 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..2e1baef7ea 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3. 
+ * 
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,43 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+//static int	search_str_set(char *pattern, StringSet *str_set, int *initial_orders);
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +744,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +850,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +863,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +939,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +997,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1033,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1053,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1103,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1124,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1228,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2192,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2362,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2540,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2638,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2832,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2872,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2972,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3334,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3654,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3768,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3849,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3873,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3910,1039 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+					new = makeStringInfo();
+					appendStringInfoString(new, old->data);
+					string_set_add(new_str_set, new);
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{						
+						/* copy source string */
+						new = makeStringInfo();
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c92d0631a0..ebb017b015 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10425,6 +10425,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 108d69ba28..28d098b1cf 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sun_Oct_22_11_39_20_2023_140)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v10-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v10 4/7] Row pattern recognition patch (executor).
@ 2023-10-22 02:22 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-10-22 02:22 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1363 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1419 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..2e1baef7ea 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3. 
+ * 
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,43 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+//static int	search_str_set(char *pattern, StringSet *str_set, int *initial_orders);
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +744,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +850,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +863,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +939,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +997,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1033,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1053,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1103,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1124,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1228,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2192,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2362,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2540,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2638,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2832,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2872,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2972,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3334,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3654,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3768,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3849,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3873,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3910,1039 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		int64	result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		int64		i;
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+					new = makeStringInfo();
+					appendStringInfoString(new, old->data);
+					string_set_add(new_str_set, new);
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{						
+						/* copy source string */
+						new = makeStringInfo();
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		pfree(str->data);
+		pfree(str);
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index b87a624fb2..9ebcc7b5d2 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
 #include "windowapi.h"
@@ -36,11 +39,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -673,7 +684,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -713,3 +724,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index c92d0631a0..ebb017b015 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10425,6 +10425,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 108d69ba28..28d098b1cf 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sun_Oct_22_11_39_20_2023_140)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v10-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v11 4/7] Row pattern recognition patch (executor).
@ 2023-11-08 06:57 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-11-08 06:57 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1420 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1476 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..ea5e73c969 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,42 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +743,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +849,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +862,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +938,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +996,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1032,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1052,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1102,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1123,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1227,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2191,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2361,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2539,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2637,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2831,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2871,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2971,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3333,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3653,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3767,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3848,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3872,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3909,1097 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+	bool		greedy = false;
+	int64		result_pos, i;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier.
+	 * If it does not, we can just apply the pattern to each row.
+	 * If it succeeds, we are done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	*quantifier = strVal(lfirst(lc1));
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s", pos, vname);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+				elog(DEBUG1, "expression result is false or out of frame");
+				register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+		elog(DEBUG1, "pattern matched");
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included.
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 0bfbac00d7..a4a11c7f8d 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f14aed422a..6df54a3cab 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10423,6 +10423,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5d7f17dee0..d8f92720bc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_Nov__8_16_37_05_2023_872)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v11-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v11 4/7] Row pattern recognition patch (executor).
@ 2023-11-08 06:57 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-11-08 06:57 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1420 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1476 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..ea5e73c969 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,42 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +743,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +849,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +862,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +938,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +996,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1032,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1052,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1102,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1123,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1227,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2191,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2361,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2539,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2637,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2831,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2871,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2971,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3333,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3653,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3767,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3848,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3872,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3909,1097 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+	bool		greedy = false;
+	int64		result_pos, i;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier.
+	 * If it does not, we can just apply the pattern to each row.
+	 * If it succeeds, we are done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	*quantifier = strVal(lfirst(lc1));
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s", pos, vname);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+				elog(DEBUG1, "expression result is false or out of frame");
+				register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+		elog(DEBUG1, "pattern matched");
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included.
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 0bfbac00d7..a4a11c7f8d 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f14aed422a..6df54a3cab 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10423,6 +10423,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5d7f17dee0..d8f92720bc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_Nov__8_16_37_05_2023_872)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v11-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v11 4/7] Row pattern recognition patch (executor).
@ 2023-11-08 06:57 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-11-08 06:57 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1420 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1476 insertions(+), 13 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 77724a6daa..ea5e73c969 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,42 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +743,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +849,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +862,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -861,8 +938,10 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * If we created a mark pointer for aggregates, keep it pushed up to frame
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
+#ifdef NOT_USED
 	if (agg_winobj->markptr >= 0)
 		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+#endif
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +996,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1032,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1052,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1102,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1123,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1227,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2191,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2361,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2539,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2637,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2831,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2871,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2971,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3333,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3653,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3767,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3848,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3872,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3909,1097 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+	bool		greedy = false;
+	int64		result_pos, i;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier.
+	 * If it does not, we can just apply the pattern to each row.
+	 * If it succeeds, we are done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	*quantifier = strVal(lfirst(lc1));
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s", pos, vname);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+				elog(DEBUG1, "expression result is false or out of frame");
+				register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+		elog(DEBUG1, "pattern matched");
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included.
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 0bfbac00d7..a4a11c7f8d 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f14aed422a..6df54a3cab 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10423,6 +10423,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5d7f17dee0..d8f92720bc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_Nov__8_16_37_05_2023_872)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v11-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v12 4/7] Row pattern recognition patch (executor).
@ 2023-12-04 11:23 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-12-04 11:23 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1435 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1490 insertions(+), 14 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3258305f57..a093e5dec6 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,42 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +743,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +849,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +862,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +939,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64	markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1009,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1045,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1065,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1115,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1136,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1240,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2204,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2374,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2552,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2650,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2844,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2884,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2984,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3346,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3666,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3780,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3861,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3885,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3922,1097 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+	bool		greedy = false;
+	int64		result_pos, i;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier.
+	 * If it does not, we can just apply the pattern to each row.
+	 * If it succeeds, we are done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	*quantifier = strVal(lfirst(lc1));
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s", pos, vname);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+				elog(DEBUG1, "expression result is false or out of frame");
+				register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+		elog(DEBUG1, "pattern matched");
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included.
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 0bfbac00d7..a4a11c7f8d 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..aec365cf07 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10437,6 +10437,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5d7f17dee0..d8f92720bc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Dec__8_10_16_13_2023_489)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v12-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v12 4/7] Row pattern recognition patch (executor).
@ 2023-12-04 11:23 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-12-04 11:23 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1435 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1490 insertions(+), 14 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3258305f57..a093e5dec6 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,42 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +743,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +849,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +862,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +939,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64	markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1009,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1045,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1065,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1115,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1136,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1240,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2204,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2374,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2552,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2650,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2844,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2884,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2984,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3346,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3666,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3780,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3861,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3885,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3922,1097 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+	bool		greedy = false;
+	int64		result_pos, i;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier.
+	 * If it does not, we can just apply the pattern to each row.
+	 * If it succeeds, we are done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	*quantifier = strVal(lfirst(lc1));
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s", pos, vname);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+				elog(DEBUG1, "expression result is false or out of frame");
+				register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+		elog(DEBUG1, "pattern matched");
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included.
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 0bfbac00d7..a4a11c7f8d 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..aec365cf07 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10437,6 +10437,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5d7f17dee0..d8f92720bc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Dec__8_10_16_13_2023_489)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v12-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v12 4/7] Row pattern recognition patch (executor).
@ 2023-12-04 11:23 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2023-12-04 11:23 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1435 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1490 insertions(+), 14 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3258305f57..a093e5dec6 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,42 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +743,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +849,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +862,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +939,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64	markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1009,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1045,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1065,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1115,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1136,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1240,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2204,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2374,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2552,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2650,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2844,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2884,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2984,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3346,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3666,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3780,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3861,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3885,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3922,1097 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+	bool		greedy = false;
+	int64		result_pos, i;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier.
+	 * If it does not, we can just apply the pattern to each row.
+	 * If it succeeds, we are done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	*quantifier = strVal(lfirst(lc1));
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s", pos, vname);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+				elog(DEBUG1, "expression result is false or out of frame");
+				register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+		elog(DEBUG1, "pattern matched");
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included.
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 0bfbac00d7..a4a11c7f8d 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fb58dee3bc..aec365cf07 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10437,6 +10437,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 5d7f17dee0..d8f92720bc 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Dec__8_10_16_13_2023_489)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v12-0005-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v13 5/8] Row pattern recognition patch (executor).
@ 2024-01-22 09:45 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-01-22 09:45 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1435 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1490 insertions(+), 14 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 62d028379b..28d1110b8a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,42 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +743,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +849,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +862,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +939,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64	markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1009,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1045,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1065,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1115,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1136,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1240,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2204,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2374,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2552,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2650,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2844,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2884,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2984,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3346,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3666,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3780,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3861,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3885,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3922,1097 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+	bool		greedy = false;
+	int64		result_pos, i;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier.
+	 * If it does not, we can just apply the pattern to each row.
+	 * If it succeeds, we are done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	*quantifier = strVal(lfirst(lc1));
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s", pos, vname);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+				elog(DEBUG1, "expression result is false or out of frame");
+				register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+		elog(DEBUG1, "pattern matched");
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included.
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 095de7741d..67a890f992 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ad74e07dbb..a34ddaa385 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10452,6 +10452,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 561fdd98f1..888dad4c7d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Jan_22_19_26_18_2024_011)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v13-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v13 5/8] Row pattern recognition patch (executor).
@ 2024-01-22 09:45 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-01-22 09:45 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1435 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1490 insertions(+), 14 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 62d028379b..28d1110b8a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,42 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +743,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +849,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +862,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +939,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64	markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1009,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1045,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1065,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1115,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1136,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1240,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2204,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2374,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2552,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2650,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2844,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2884,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2984,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3346,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3666,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3780,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3861,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3885,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3922,1097 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+	bool		greedy = false;
+	int64		result_pos, i;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier.
+	 * If it does not, we can just apply the pattern to each row.
+	 * If it succeeds, we are done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	*quantifier = strVal(lfirst(lc1));
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s", pos, vname);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+				elog(DEBUG1, "expression result is false or out of frame");
+				register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+		elog(DEBUG1, "pattern matched");
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included.
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 095de7741d..67a890f992 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ad74e07dbb..a34ddaa385 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10452,6 +10452,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 561fdd98f1..888dad4c7d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Jan_22_19_26_18_2024_011)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v13-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v13 5/8] Row pattern recognition patch (executor).
@ 2024-01-22 09:45 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-01-22 09:45 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1435 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   26 +
 4 files changed, 1490 insertions(+), 14 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 62d028379b..28d1110b8a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,40 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet {
+	StringInfo	*str_set;
+	Size		set_size;	/* current array allocation size in number of items */
+	int			set_index;	/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26	/* we allow [a-z] variable initials */
+typedef struct VariablePos {
+	int			pos[NUM_ALPHABETS];	/* postion(s) in PATTERN */
+} VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -182,8 +218,9 @@ static void begin_partition(WindowAggState *winstate);
 static void spool_tuples(WindowAggState *winstate, int64 pos);
 static void release_partition(WindowAggState *winstate);
 
-static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
+static int  row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +232,42 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
-static bool window_gettupleslot(WindowObject winobj, int64 pos,
-								TupleTableSlot *slot);
 
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
+static bool window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot);
+
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos);
+static char	pattern_initial(WindowAggState *winstate, char *vname);
+static int do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial, int pos);
+static bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2);
+static int variable_pos_fetch(VariablePos *variable_pos, char initial, int index);
+static void variable_pos_discard(VariablePos *variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +743,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +849,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +862,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +939,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64	markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1009,29 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate, winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1045,11 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT, winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1065,28 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj, winstate->aggregatedupto);
+			if (ret == -1)	/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1115,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1136,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP
+		 * TO PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1240,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2204,8 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT , winstate->currentpos);
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2374,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear existing
+			 * reduced frame info so that we newly calculate the info starting from
+			 * current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2552,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry	*te;
+	Expr		*expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2650,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2844,39 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char		*name;
+			ExprState	*exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+			elog(DEBUG1, "defineVariable name: %s", name);
+			winstate->defineVariableList = lappend(winstate->defineVariableList,
+												   makeString(pstrdup(name)));
+			attno_map((Node *)expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList = lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2884,57 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr	*func;
+	int			nargs;
+	Expr		*expr;
+	Var			*var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *)node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args", func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible that arg type is Const? */
+			var = (Var *)expr;
+
+			if (func->funcid == F_PREV)
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +2984,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3346,7 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,  pos, winobj->markpos );
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3666,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+					 int relpos, int seektype, bool set_mark,
+					 bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3780,21 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/* RPR cares about frame head pos. Need to call update_frameheadpos */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3861,12 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj, winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos + num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3885,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3922,1097 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame according
+ * to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int		state;
+	int		rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+		int64	i;
+		int		num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1; get_reduced_frame_map(winstate,i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT, state, pos);
+			break;
+	}
+
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT, rtn, pos);
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * Create reduced frame map
+ */
+static
+void create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext, REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * Clear reduced frame map
+ */
+static
+void clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * Get reduced frame map specified by pos
+ */
+static
+int get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64	realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell	*lc1, *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet	*str_set;
+	int			str_set_index = 0;
+	int			initial_index;
+	VariablePos	*variable_pos;
+	bool		greedy = false;
+	int64		result_pos, i;
+
+	/*
+	 * Set of pattern variables evaluated to true.
+	 * Each character corresponds to pattern variable.
+	 * Example:
+	 * str_set[0] = "AB";
+	 * str_set[1] = "AC";
+	 * In this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier.
+	 * If it does not, we can just apply the pattern to each row.
+	 * If it succeeds, we are done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	*quantifier = strVal(lfirst(lc1));
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s", pos, vname);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+				elog(DEBUG1, "expression result is false or out of frame");
+				register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+		elog(DEBUG1, "pattern matched");
+
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included.
+	 * Loop over until none of pattern matches or encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+		{
+			char	*vname = strVal(lfirst(lc1));
+			char	*quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s", pos, vname, quantifier);
+
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname, encoded_str, &expression_result);
+			if (expression_result)
+			{
+				elog(DEBUG1, "expression result is true");
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			 if (result_pos < 0)
+				 break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+		elog(DEBUG1, "pos: " INT64_FORMAT " str_set_index: %d encoded_str: %s", pos, str_set_index, encoded_str->data);
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s", pos, encoded_str->data);
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth (lc1, winstate->patternVariableList, lc2, winstate->patternRegexpList)
+	{
+		char	*vname = strVal(lfirst(lc1));
+		char	*quantifier = strVal(lfirst(lc2));
+		char	 initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s", pos, pattern_str->data);
+
+	/* look for matching pattern variable sequence */
+	elog(DEBUG1, "search_str_set started");
+	num_matched_rows = search_str_set(pattern_str->data, str_set, variable_pos);
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * Perform pattern matching using pattern against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. For example, if DEFINE has variables START, UP and
+ * DOWN, PATTERN HAS START, UP and DOWN, then the initials in PATTERN will be
+ * 'a', 'b' and 'c'.
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows.
+ */
+static
+int search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'	/* a pattern is freezed if it ends with the char */
+#define	DISCARD_CHAR	'z'	/* a pattern is not need to keep */
+
+	int			set_size;	/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet	*old_str_set, *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;	/* search target row */
+		char	*p;
+		int		old_set_size;
+		int		i;
+
+		elog(DEBUG1, "index: %d", index);
+
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+				p++;	/* next pattern variable */
+			}
+		}
+		else	/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size ; i++)
+			{
+				StringInfo	new;
+				char	last_old_char;
+				int		old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+						elog(DEBUG1, "discard this old set because shorter match: %s", old->data);
+						continue;
+					}
+
+					elog(DEBUG1, "keep this old set: %s", old->data);
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+					elog(DEBUG1, "discard this old set: %s", old->data);
+					continue;
+				}
+
+				elog(DEBUG1, "str->data: %s", str->data);
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+						elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+							elog(DEBUG1, "discard this new data: %s",
+								new->data);
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int		new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed
+							 * entries that have shorter match length.
+							 * Mark them as "discard" so that they are
+							 * discarded in the next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size = string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size; new_index++)
+							{
+								char	new_last_char;
+								int		new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char = new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/* mark this set to discard in the next round */
+										appendStringInfoChar(new, DISCARD_CHAR);
+										elog(DEBUG1, "add discard char: %s", new->data);
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;	/* no data */
+
+		elog(DEBUG1, "target string: %s", s->data);
+
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum	d;
+	text	*res;
+	char	*substr;
+	int		len = 0;
+	text	*pattern_text, *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the
+	 * matched string is. That is the number of rows in the reduced window
+	 * frame.  The reason why we can't call textregexsubstr in the first
+	 * place is, it errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+
+/*
+ * Evaluate expression associated with PATTERN variable vname.
+ * relpos is relative row position in a frame (starting from 0).
+ * "quantifier" is the quatifier part of the PATTERN regular expression.
+ * Currently only '+' is allowed.
+ * result is out paramater representing the expression evaluation result
+ * is true of false.
+ * Return values are:
+ * >=0: the last match absolute row position
+ * other wise out of frame.
+ */
+static
+int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+						char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState	*winstate = winobj->winstate;
+	ExprContext		*econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell		*lc1, *lc2, *lc3;
+	ExprState		*pat;
+	Datum			eval_result;
+	bool			out_of_frame = false;
+	bool			isnull;
+	TupleTableSlot *slot;
+
+	forthree (lc1, winstate->defineVariableList, lc2, winstate->defineClauseList, lc3, winstate->defineInitial)
+	{
+		char	initial;
+		char	*name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT, vname, current_pos);
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT, vname, current_pos);
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT, vname, current_pos);
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int		ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT, current_pos);
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT, current_pos);
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT, current_pos - 1);
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT, current_pos - 1);
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT, current_pos + 1);
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT, current_pos + 1);
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char	pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char		*name;
+	ListCell	*lc1, *lc2;
+
+	forboth (lc1, winstate->defineVariableList, lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1));				/* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));		/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+				return initial;					/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet	*string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size	set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 ||index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * Returns the size of StringSet.
+ */
+static
+int string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void string_set_discard(StringSet *string_set)
+{
+	int		i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo str = string_set->str_set[i];
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *variable_pos_init(void)
+{
+	VariablePos	*variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int		index = initial - 'a';
+	int		slot;
+	int		i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int	index1, index2;
+	int pos1, pos2;
+
+	for (index1 = 0; ; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0; ; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int		pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * Discard VariablePos
+ */
+static
+void variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 095de7741d..67a890f992 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;	/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ad74e07dbb..a34ddaa385 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10452,6 +10452,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 561fdd98f1..888dad4c7d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2470,6 +2470,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2518,6 +2523,15 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions ('+' or ''. list of String) */
+	List	   *defineVariableList;	/* list of row pattern definition variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;		/* list of row pattern definition variable initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2554,6 +2568,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char		*reduced_frame_map;
+	int64		alloc_sz;	/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Jan_22_19_26_18_2024_011)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v13-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v14 5/8] Row pattern recognition patch (executor).
@ 2024-02-28 13:59 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-02-28 13:59 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 62d028379b..c473cbd218 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2668,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 095de7741d..c5bd28ce19 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9c120fc2b7..81e4580b13 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10456,6 +10456,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 444a5f0fd5..15d8ac4c1e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2477,6 +2477,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2525,6 +2530,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2561,6 +2579,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Feb_29_09_19_54_2024_640)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v14-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v14 5/8] Row pattern recognition patch (executor).
@ 2024-02-28 13:59 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-02-28 13:59 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 62d028379b..c473cbd218 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2668,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 095de7741d..c5bd28ce19 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9c120fc2b7..81e4580b13 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10456,6 +10456,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 444a5f0fd5..15d8ac4c1e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2477,6 +2477,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2525,6 +2530,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2561,6 +2579,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Feb_29_09_19_54_2024_640)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v14-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v14 5/8] Row pattern recognition patch (executor).
@ 2024-02-28 13:59 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-02-28 13:59 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 62d028379b..c473cbd218 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2483,6 +2668,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 095de7741d..c5bd28ce19 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/builtins.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9c120fc2b7..81e4580b13 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10456,6 +10456,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 444a5f0fd5..15d8ac4c1e 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2477,6 +2477,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2525,6 +2530,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2561,6 +2579,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Feb_29_09_19_54_2024_640)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v14-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v15 5/8] Row pattern recognition patch (executor).
@ 2024-03-28 10:30 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-03-28 10:30 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..9606e7d463 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2671,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07023ee61d..b9d171e327 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10474,6 +10474,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1774c56ae3..f2322ace6f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2549,6 +2549,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2597,6 +2602,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2633,6 +2651,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Mar_28_19_59_25_2024_076)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v15-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v15 5/8] Row pattern recognition patch (executor).
@ 2024-03-28 10:30 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-03-28 10:30 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..9606e7d463 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2671,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07023ee61d..b9d171e327 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10474,6 +10474,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1774c56ae3..f2322ace6f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2549,6 +2549,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2597,6 +2602,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2633,6 +2651,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Mar_28_19_59_25_2024_076)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v15-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v15 5/8] Row pattern recognition patch (executor).
@ 2024-03-28 10:30 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-03-28 10:30 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..9606e7d463 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2671,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07023ee61d..b9d171e327 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10474,6 +10474,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1774c56ae3..f2322ace6f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2549,6 +2549,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2597,6 +2602,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2633,6 +2651,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Mar_28_19_59_25_2024_076)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v15-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v15 5/8] Row pattern recognition patch (executor).
@ 2024-03-28 10:30 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-03-28 10:30 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..9606e7d463 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2671,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07023ee61d..b9d171e327 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10474,6 +10474,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1774c56ae3..f2322ace6f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2549,6 +2549,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2597,6 +2602,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2633,6 +2651,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Mar_28_19_59_25_2024_076)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v15-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v15 5/8] Row pattern recognition patch (executor).
@ 2024-03-28 10:30 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-03-28 10:30 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..9606e7d463 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2671,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 07023ee61d..b9d171e327 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10474,6 +10474,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1774c56ae3..f2322ace6f 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2549,6 +2549,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2597,6 +2602,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2633,6 +2651,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Mar_28_19_59_25_2024_076)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v15-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* Re: postgres_fdw fails because GMT != UTC
@ 2024-04-04 09:08 Daniel Gustafsson <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Daniel Gustafsson @ 2024-04-04 09:08 UTC (permalink / raw)
  To: Tom Lane <[email protected]>; +Cc: [email protected]

> On 4 Apr 2024, at 08:19, Tom Lane <[email protected]> wrote:
> 
> Over at [1] we have a complaint of postgres_fdw failing with
> a remote-server error
> 
>> ERROR:  invalid value for parameter "TimeZone": "UTC"
> 
> I am not quite clear on how broken an installation needs to be to
> reject "UTC" as a time zone setting, except that the breakage cannot
> be subtle.  However, I notice that our code in pgtz.c and other
> places treats "GMT" as a hard-wired special case ... but not "UTC".
> I wonder if we ought to modify those places to force "UTC" down the
> same hard-wired paths.  If we acted like that, this would have worked
> no matter how misconfigured the installation was.

+1. It makes little sense to support GMT like that but not UTC.

> An alternative answer could be to change postgres_fdw to send "GMT"
> not "UTC".  That's ugly from a standards-compliance viewpoint, but
> it would fix this problem even with a non-updated remote server,
> and I think postgres_fdw is generally intended to work with even
> very old remote servers.

There is always a risk in accomodating broken installations that it might hide
other subtle bugs, but off the cuff that risk seems quite low in this case.

--
Daniel Gustafsson







^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v16 5/8] Row pattern recognition patch (executor).
@ 2024-04-12 06:49 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-04-12 06:49 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..9606e7d463 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2671,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..5f7fb538f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10477,6 +10477,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index fa2f70b7a4..053ad1764c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Apr_12_16_09_08_2024_262)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v16-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v16 5/8] Row pattern recognition patch (executor).
@ 2024-04-12 06:49 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-04-12 06:49 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..9606e7d463 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2671,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..5f7fb538f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10477,6 +10477,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index fa2f70b7a4..053ad1764c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Apr_12_16_09_08_2024_262)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v16-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v16 5/8] Row pattern recognition patch (executor).
@ 2024-04-12 06:49 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-04-12 06:49 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1617 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1679 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..9606e7d463 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -673,6 +752,7 @@ eval_windowaggregates(WindowAggState *winstate)
 	WindowObject agg_winobj;
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot;
+	bool		agg_result_isnull;
 
 	numaggs = winstate->numaggs;
 	if (numaggs == 0)
@@ -778,6 +858,9 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *
+	 *   - if RPR is enabled and skip mode is SKIP TO NEXT ROW,
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,8 +871,11 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			(rpr_is_defined(winstate) &&
+			 winstate->rpSkipTo == ST_NEXT_ROW))
 		{
+			elog(DEBUG1, "peraggstate->restart is set");
 			peraggstate->restart = true;
 			numaggs_restart++;
 		}
@@ -862,7 +948,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1018,30 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
+	}
+
+	agg_result_isnull = false;
+	/* RPR is defined? */
+	if (rpr_is_defined(winstate))
+	{
+		/*
+		 * If the skip mode is SKIP TO PAST LAST ROW and we already know that
+		 * current row is a skipped row, we don't need to accumulate rows,
+		 * just return NULL. Note that for unamtched row, we need to do
+		 * aggregation so that count(*) shows 0, rather than NULL.
+		 */
+		if (winstate->rpSkipTo == ST_PAST_LAST_ROW &&
+			get_reduced_frame_map(winstate,
+								  winstate->currentpos) == RF_SKIPPED)
+			agg_result_isnull = true;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+		elog(DEBUG1, "===== loop in frame starts: " INT64_FORMAT,
+			 winstate->aggregatedupto);
+
+		if (agg_result_isnull)
+			break;
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,32 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -996,6 +1151,16 @@ next_tuple:
 								 peraggstate,
 								 result, isnull);
 
+		/*
+		 * RPR is defined and we just return NULL because skip mode is SKIP TO
+		 * PAST LAST ROW and current row is skipped row.
+		 */
+		if (agg_result_isnull)
+		{
+			*isnull = true;
+			*result = (Datum) 0;
+		}
+
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1255,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2219,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2392,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2570,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2671,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2862,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2906,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3013,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3375,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3696,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3810,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3895,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3921,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3958,1249 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..5f7fb538f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10477,6 +10477,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index fa2f70b7a4..053ad1764c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Apr_12_16_09_08_2024_262)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v16-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v17 5/8] Row pattern recognition patch (executor).
@ 2024-04-28 11:00 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-04-28 11:00 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..5f7fb538f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10477,6 +10477,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d927ac44a8..971d8682b1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2550,6 +2550,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2598,6 +2603,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2634,6 +2652,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sun_Apr_28_20_28_26_2024_444)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v17-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v17 5/8] Row pattern recognition patch (executor).
@ 2024-04-28 11:00 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-04-28 11:00 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..5f7fb538f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10477,6 +10477,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d927ac44a8..971d8682b1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2550,6 +2550,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2598,6 +2603,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2634,6 +2652,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sun_Apr_28_20_28_26_2024_444)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v17-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v17 5/8] Row pattern recognition patch (executor).
@ 2024-04-28 11:00 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-04-28 11:00 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..5f7fb538f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10477,6 +10477,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d927ac44a8..971d8682b1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2550,6 +2550,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2598,6 +2603,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2634,6 +2652,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sun_Apr_28_20_28_26_2024_444)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v17-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v18 5/8] Row pattern recognition patch (executor).
@ 2024-05-11 07:11 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-05-11 07:11 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..5f7fb538f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10477,6 +10477,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8bc421e7c0..4dd9a17eca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_May_11_16_23_07_2024_789)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v18-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v18 5/8] Row pattern recognition patch (executor).
@ 2024-05-11 07:11 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-05-11 07:11 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..5f7fb538f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10477,6 +10477,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8bc421e7c0..4dd9a17eca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_May_11_16_23_07_2024_789)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v18-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v18 5/8] Row pattern recognition patch (executor).
@ 2024-05-11 07:11 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-05-11 07:11 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 134e3b22fd..5f7fb538f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10477,6 +10477,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8bc421e7c0..4dd9a17eca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_May_11_16_23_07_2024_789)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v18-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v19 5/8] Row pattern recognition patch (executor).
@ 2024-05-14 23:26 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-05-14 23:26 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2a9f2105b1..595ef7502e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10479,6 +10479,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8bc421e7c0..4dd9a17eca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_May_15_09_02_03_2024_008)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v19-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v19 5/8] Row pattern recognition patch (executor).
@ 2024-05-14 23:26 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-05-14 23:26 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2a9f2105b1..595ef7502e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10479,6 +10479,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8bc421e7c0..4dd9a17eca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_May_15_09_02_03_2024_008)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v19-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v19 5/8] Row pattern recognition patch (executor).
@ 2024-05-14 23:26 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-05-14 23:26 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2a9f2105b1..595ef7502e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10479,6 +10479,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8bc421e7c0..4dd9a17eca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_May_15_09_02_03_2024_008)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v19-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v20 5/8] Row pattern recognition patch (executor).
@ 2024-05-24 02:26 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-05-24 02:26 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6a5476d3c4..5e7506dabb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10479,6 +10479,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8bc421e7c0..4dd9a17eca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_May_24_11_39_19_2024_763)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v20-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v20 5/8] Row pattern recognition patch (executor).
@ 2024-05-24 02:26 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-05-24 02:26 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6a5476d3c4..5e7506dabb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10479,6 +10479,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8bc421e7c0..4dd9a17eca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_May_24_11_39_19_2024_763)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v20-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v20 5/8] Row pattern recognition patch (executor).
@ 2024-05-24 02:26 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-05-24 02:26 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6a5476d3c4..5e7506dabb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10479,6 +10479,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8bc421e7c0..4dd9a17eca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_May_24_11_39_19_2024_763)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v20-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v21 5/8] Row pattern recognition patch (executor).
@ 2024-08-26 04:32 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-08-26 04:32 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4abc6d9526..c3fafed291 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10549,6 +10549,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index af7d8fd1e7..609b066845 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2578,6 +2578,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2626,6 +2631,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2662,6 +2680,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Aug_26_13_39_47_2024_878)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v21-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v21 5/8] Row pattern recognition patch (executor).
@ 2024-08-26 04:32 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-08-26 04:32 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4abc6d9526..c3fafed291 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10549,6 +10549,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index af7d8fd1e7..609b066845 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2578,6 +2578,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2626,6 +2631,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2662,6 +2680,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Aug_26_13_39_47_2024_878)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v21-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v21 5/8] Row pattern recognition patch (executor).
@ 2024-08-26 04:32 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-08-26 04:32 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4abc6d9526..c3fafed291 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10549,6 +10549,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index af7d8fd1e7..609b066845 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2578,6 +2578,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2626,6 +2631,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2662,6 +2680,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Aug_26_13_39_47_2024_878)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v21-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v22 5/8] Row pattern recognition patch (executor).
@ 2024-09-19 04:48 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-09-19 04:48 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 51a6708a39..e46a3dd1b7 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1199,6 +1353,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2325,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2438,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2445,6 +2616,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2543,6 +2717,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2724,6 +2908,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2732,6 +2953,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2789,6 +3068,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3149,7 +3430,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3469,14 +3751,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3543,11 +3865,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3614,6 +3950,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3632,15 +3976,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3671,3 +4013,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 43f608d7a0..6a301920f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10604,6 +10604,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 88467977f8..058cef2121 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2581,6 +2581,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2640,6 +2645,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2667,6 +2685,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Sep_19_13_59_47_2024_608)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v22-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v22 5/8] Row pattern recognition patch (executor).
@ 2024-09-19 04:48 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-09-19 04:48 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 51a6708a39..e46a3dd1b7 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1199,6 +1353,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2325,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2438,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2445,6 +2616,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2543,6 +2717,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2724,6 +2908,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2732,6 +2953,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2789,6 +3068,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3149,7 +3430,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3469,14 +3751,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3543,11 +3865,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3614,6 +3950,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3632,15 +3976,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3671,3 +4013,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 43f608d7a0..6a301920f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10604,6 +10604,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 88467977f8..058cef2121 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2581,6 +2581,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2640,6 +2645,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2667,6 +2685,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Sep_19_13_59_47_2024_608)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v22-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v22 5/8] Row pattern recognition patch (executor).
@ 2024-09-19 04:48 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-09-19 04:48 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 51a6708a39..e46a3dd1b7 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1199,6 +1353,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2325,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2438,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2445,6 +2616,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2543,6 +2717,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2724,6 +2908,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2732,6 +2953,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2789,6 +3068,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3149,7 +3430,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3469,14 +3751,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3543,11 +3865,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3614,6 +3950,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3632,15 +3976,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3671,3 +4013,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 43f608d7a0..6a301920f9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10604,6 +10604,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 88467977f8..058cef2121 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2581,6 +2581,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2640,6 +2645,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2667,6 +2685,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Sep_19_13_59_47_2024_608)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v22-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v23 5/8] Row pattern recognition patch (executor).
@ 2024-10-25 03:56 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-10-25 03:56 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1676 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1738 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 51a6708a39..e6f4fc1a7a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,53 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+}			NavigationInfo;
+
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +233,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +245,51 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +866,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +882,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +957,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1027,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1048,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1069,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1144,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1368,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2340,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2453,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2445,6 +2631,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2543,6 +2732,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2724,6 +2923,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2732,6 +2968,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2789,6 +3130,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3149,7 +3492,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3469,14 +3813,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3543,11 +3927,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3614,6 +4012,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3632,15 +4038,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3671,3 +4075,1257 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+											  regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1ec0d6f6b5..754e855552 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10635,6 +10635,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e4698a28c4..9a2f8254d1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2589,6 +2589,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2648,6 +2653,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2675,6 +2693,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Oct_25_13_04_53_2024_648)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v23-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v23 5/8] Row pattern recognition patch (executor).
@ 2024-10-25 03:56 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-10-25 03:56 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1676 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1738 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 51a6708a39..e6f4fc1a7a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,53 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+}			NavigationInfo;
+
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +233,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +245,51 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +866,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +882,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +957,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1027,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1048,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1069,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1144,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1368,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2340,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2453,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2445,6 +2631,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2543,6 +2732,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2724,6 +2923,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2732,6 +2968,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2789,6 +3130,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3149,7 +3492,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3469,14 +3813,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3543,11 +3927,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3614,6 +4012,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3632,15 +4038,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3671,3 +4075,1257 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+											  regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1ec0d6f6b5..754e855552 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10635,6 +10635,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e4698a28c4..9a2f8254d1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2589,6 +2589,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2648,6 +2653,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2675,6 +2693,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Oct_25_13_04_53_2024_648)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v23-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v23 5/8] Row pattern recognition patch (executor).
@ 2024-10-25 03:56 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-10-25 03:56 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1676 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1738 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 51a6708a39..e6f4fc1a7a 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,53 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+}			NavigationInfo;
+
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +233,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +245,51 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +866,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +882,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +957,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1027,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1048,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1069,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1144,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1368,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2340,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2453,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2445,6 +2631,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2543,6 +2732,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2724,6 +2923,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2732,6 +2968,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2789,6 +3130,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3149,7 +3492,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3469,14 +3813,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3543,11 +3927,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3614,6 +4012,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3632,15 +4038,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3671,3 +4075,1257 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+											  regexp_instr, DEFAULT_COLLATION_OID,
+											  PointerGetDatum(encoded_str_text),
+											  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1ec0d6f6b5..754e855552 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10635,6 +10635,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index e4698a28c4..9a2f8254d1 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2589,6 +2589,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2648,6 +2653,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2675,6 +2693,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Fri_Oct_25_13_04_53_2024_648)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v23-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v24 5/8] Row pattern recognition patch (executor).
@ 2024-12-19 06:06 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-19 06:06 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1697 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1759 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..3c2e95f46e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -33,9 +33,13 @@
  */
 #include "postgres.h"
 
+#include <regex.h>
+#include <sys/types.h>
+
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +52,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +164,58 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+}			NavigationInfo;
+
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
+/*
+ * Regular expression compiled cache for do_pattern_match exists
+ */
+static regex_t *regcache = NULL;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +241,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +253,52 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+static void do_pattern_match_finish(void);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +875,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +891,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +966,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1036,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1057,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1078,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1153,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1377,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2349,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2462,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2639,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2740,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2931,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2976,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3138,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3500,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3821,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3935,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4020,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4046,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4083,1269 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chance to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+
+		/*
+		 * If the string is already freeze, we don't need to check it by
+		 * do_pattern_match because it has been ready checked.
+		 */
+		if (s->data[s->len] == FREEZED_CHAR)
+			len = s->len - 1;
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		else if (s->data[s->len] == DISCARD_CHAR)
+			continue;
+
+		else
+			len = do_pattern_match(pattern, s->data);
+
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	do_pattern_match_finish();
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	static regex_t preg;
+	int			len;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+
+	/*
+	 * Compile regexp if it does not exist.
+	 */
+	if (regcache == NULL)
+	{
+		if (regcomp(&preg, pattern, cflags))
+		{
+			elog(ERROR, "failed to compile pattern: %s", pattern);
+		}
+		regcache = &preg;
+	}
+
+	if (regexec(&preg, encoded_str, nmatch, pmatch, eflags))
+		return 0;				/* does not match */
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+}
+
+static
+void
+do_pattern_match_finish(void)
+{
+	if (regcache != NULL)
+		regfree(regcache);
+	regcache = NULL;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0f22c21723..87b2c1557f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10651,6 +10651,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7f71b7625d..a8e2404f83 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2587,6 +2587,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2646,6 +2651,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2673,6 +2691,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Dec_19_15_19_50_2024_894)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v24-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v24 5/8] Row pattern recognition patch (executor).
@ 2024-12-19 06:06 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-19 06:06 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1697 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1759 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..3c2e95f46e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -33,9 +33,13 @@
  */
 #include "postgres.h"
 
+#include <regex.h>
+#include <sys/types.h>
+
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +52,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +164,58 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+}			NavigationInfo;
+
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
+/*
+ * Regular expression compiled cache for do_pattern_match exists
+ */
+static regex_t *regcache = NULL;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +241,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +253,52 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+static void do_pattern_match_finish(void);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +875,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +891,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +966,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1036,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1057,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1078,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1153,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1377,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2349,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2462,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2639,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2740,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2931,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2976,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3138,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3500,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3821,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3935,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4020,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4046,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4083,1269 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chance to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+
+		/*
+		 * If the string is already freeze, we don't need to check it by
+		 * do_pattern_match because it has been ready checked.
+		 */
+		if (s->data[s->len] == FREEZED_CHAR)
+			len = s->len - 1;
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		else if (s->data[s->len] == DISCARD_CHAR)
+			continue;
+
+		else
+			len = do_pattern_match(pattern, s->data);
+
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	do_pattern_match_finish();
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	static regex_t preg;
+	int			len;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+
+	/*
+	 * Compile regexp if it does not exist.
+	 */
+	if (regcache == NULL)
+	{
+		if (regcomp(&preg, pattern, cflags))
+		{
+			elog(ERROR, "failed to compile pattern: %s", pattern);
+		}
+		regcache = &preg;
+	}
+
+	if (regexec(&preg, encoded_str, nmatch, pmatch, eflags))
+		return 0;				/* does not match */
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+}
+
+static
+void
+do_pattern_match_finish(void)
+{
+	if (regcache != NULL)
+		regfree(regcache);
+	regcache = NULL;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0f22c21723..87b2c1557f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10651,6 +10651,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7f71b7625d..a8e2404f83 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2587,6 +2587,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2646,6 +2651,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2673,6 +2691,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Dec_19_15_19_50_2024_894)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v24-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v24 5/8] Row pattern recognition patch (executor).
@ 2024-12-19 06:06 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-19 06:06 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1697 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1759 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..3c2e95f46e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -33,9 +33,13 @@
  */
 #include "postgres.h"
 
+#include <regex.h>
+#include <sys/types.h>
+
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +52,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +164,58 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+}			NavigationInfo;
+
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
+/*
+ * Regular expression compiled cache for do_pattern_match exists
+ */
+static regex_t *regcache = NULL;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +241,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +253,52 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+static void do_pattern_match_finish(void);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +875,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +891,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +966,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1036,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1057,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1078,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1153,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1377,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2349,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2462,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2639,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2740,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2931,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2976,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3138,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3500,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3821,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3935,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4020,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4046,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4083,1269 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chance to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+
+		/*
+		 * If the string is already freeze, we don't need to check it by
+		 * do_pattern_match because it has been ready checked.
+		 */
+		if (s->data[s->len] == FREEZED_CHAR)
+			len = s->len - 1;
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		else if (s->data[s->len] == DISCARD_CHAR)
+			continue;
+
+		else
+			len = do_pattern_match(pattern, s->data);
+
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	do_pattern_match_finish();
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	static regex_t preg;
+	int			len;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+
+	/*
+	 * Compile regexp if it does not exist.
+	 */
+	if (regcache == NULL)
+	{
+		if (regcomp(&preg, pattern, cflags))
+		{
+			elog(ERROR, "failed to compile pattern: %s", pattern);
+		}
+		regcache = &preg;
+	}
+
+	if (regexec(&preg, encoded_str, nmatch, pmatch, eflags))
+		return 0;				/* does not match */
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+}
+
+static
+void
+do_pattern_match_finish(void)
+{
+	if (regcache != NULL)
+		regfree(regcache);
+	regcache = NULL;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0f22c21723..87b2c1557f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10651,6 +10651,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 7f71b7625d..a8e2404f83 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2587,6 +2587,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2646,6 +2651,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2673,6 +2691,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Thu_Dec_19_15_19_50_2024_894)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v24-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v25 5/9] Row pattern recognition patch (executor).
@ 2024-12-21 06:19 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-21 06:19 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1745 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1807 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..2148d4ed1e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -45,9 +46,11 @@
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
+#include "regex/regex.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +162,58 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+} NavigationInfo;
+
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+} VariablePos;
+
+/*
+ * Regular expression compiled cache for do_pattern_match exists
+ */
+static regex_t *regcache = NULL;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +239,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +251,52 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set,
+						   VariablePos *variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str, int len);
+static void do_pattern_match_finish(void);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int	string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos *variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos *variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos *variable_pos);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +873,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +889,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +964,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1034,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1151,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1375,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2347,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2460,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2637,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2738,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2929,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2974,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3136,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3498,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3819,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3933,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4018,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4044,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4081,1319 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str;
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			destroyStringInfo(encoded_str);
+
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		destroyStringInfo(encoded_str);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	destroyStringInfo(pattern_str);
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chance to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							destroyStringInfo(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data, old->len);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+
+		/*
+		 * If the string is already freeze, we don't need to check it by
+		 * do_pattern_match because it has been ready checked.
+		 */
+		if (s->data[s->len] == FREEZED_CHAR)
+			len = s->len - 1;
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		else if (s->data[s->len] == DISCARD_CHAR)
+			continue;
+
+		else
+			len = do_pattern_match(pattern, s->data, s->len);
+
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	do_pattern_match_finish();
+	return resultlen;
+}
+
+/*
+ * do_pattern_match perform pattern match using pattern against encoded_str
+ * whose length is len bytes (without null terminate).  returns matching
+ * number of rows if matching is succeeded.  Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str, int len)
+{
+	static regex_t preg;
+	int			plen;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+	int			sts;
+	pg_wchar   *data;
+	int			data_len;
+
+
+	/*
+	 * Compile regexp if it does not exist.
+	 */
+	if (regcache == NULL)
+	{
+		/* we need to convert to char to pg_wchar */
+		plen = strlen(pattern);
+		data = (pg_wchar *) palloc((plen + 1) * sizeof(pg_wchar));
+		data_len = pg_mb2wchar_with_len(pattern, data, plen);
+		/* compile re */
+		sts = pg_regcomp(&preg, /* compiled re */
+						 data,	/* target pattern */
+						 data_len,	/* length of pattern */
+						 cflags,	/* compile option */
+						 C_COLLATION_OID	/* collation */
+			);
+		pfree(data);
+
+		if (sts != REG_OKAY)
+		{
+			/* re didn't compile (no need for pg_regfree, if so) */
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("invalid regular expression: %s", pattern)));
+		}
+		regcache = &preg;
+	}
+
+	data = (pg_wchar *) palloc((strlen(encoded_str) + 1) * sizeof(pg_wchar));
+	data_len = pg_mb2wchar_with_len(encoded_str, data, len);
+
+	/* execute the regular expression match */
+	sts = pg_regexec(
+					 &preg,		/* compiled re */
+					 data,		/* target string */
+					 data_len,	/* length of encoded_str */
+					 0,			/* search start */
+					 NULL,		/* rm details */
+					 nmatch,	/* number of match sub re */
+					 pmatch,	/* match result details */
+					 eflags);
+
+	pfree(data);
+
+	if (sts != REG_OKAY)
+	{
+		if (sts != REG_NOMATCH)
+		{
+			char		errMsg[100];
+
+			pg_regerror(sts, &preg, errMsg, sizeof(errMsg));
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("regular expression failed: %s", errMsg)));
+		}
+		return 0;				/* does not match */
+	}
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+
+}
+
+static
+void
+do_pattern_match_finish(void)
+{
+	if (regcache != NULL)
+		pg_regfree(regcache);
+	regcache = NULL;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *
+string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet *string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+			destroyStringInfo(str);
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *
+variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..3142a8bc06 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2dcc2d42da..ee11ee2c8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10664,6 +10664,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 79d5e96021..87591a8d43 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2585,6 +2585,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2644,6 +2649,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2671,6 +2689,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_Dec_21_18_20_04_2024_526)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v25-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v25 5/9] Row pattern recognition patch (executor).
@ 2024-12-21 06:19 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-21 06:19 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1745 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1807 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..2148d4ed1e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -45,9 +46,11 @@
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
+#include "regex/regex.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +162,58 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+} NavigationInfo;
+
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+} VariablePos;
+
+/*
+ * Regular expression compiled cache for do_pattern_match exists
+ */
+static regex_t *regcache = NULL;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +239,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +251,52 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set,
+						   VariablePos *variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str, int len);
+static void do_pattern_match_finish(void);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int	string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos *variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos *variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos *variable_pos);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +873,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +889,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +964,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1034,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1151,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1375,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2347,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2460,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2637,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2738,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2929,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2974,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3136,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3498,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3819,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3933,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4018,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4044,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4081,1319 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str;
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			destroyStringInfo(encoded_str);
+
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		destroyStringInfo(encoded_str);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	destroyStringInfo(pattern_str);
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chance to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							destroyStringInfo(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data, old->len);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+
+		/*
+		 * If the string is already freeze, we don't need to check it by
+		 * do_pattern_match because it has been ready checked.
+		 */
+		if (s->data[s->len] == FREEZED_CHAR)
+			len = s->len - 1;
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		else if (s->data[s->len] == DISCARD_CHAR)
+			continue;
+
+		else
+			len = do_pattern_match(pattern, s->data, s->len);
+
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	do_pattern_match_finish();
+	return resultlen;
+}
+
+/*
+ * do_pattern_match perform pattern match using pattern against encoded_str
+ * whose length is len bytes (without null terminate).  returns matching
+ * number of rows if matching is succeeded.  Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str, int len)
+{
+	static regex_t preg;
+	int			plen;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+	int			sts;
+	pg_wchar   *data;
+	int			data_len;
+
+
+	/*
+	 * Compile regexp if it does not exist.
+	 */
+	if (regcache == NULL)
+	{
+		/* we need to convert to char to pg_wchar */
+		plen = strlen(pattern);
+		data = (pg_wchar *) palloc((plen + 1) * sizeof(pg_wchar));
+		data_len = pg_mb2wchar_with_len(pattern, data, plen);
+		/* compile re */
+		sts = pg_regcomp(&preg, /* compiled re */
+						 data,	/* target pattern */
+						 data_len,	/* length of pattern */
+						 cflags,	/* compile option */
+						 C_COLLATION_OID	/* collation */
+			);
+		pfree(data);
+
+		if (sts != REG_OKAY)
+		{
+			/* re didn't compile (no need for pg_regfree, if so) */
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("invalid regular expression: %s", pattern)));
+		}
+		regcache = &preg;
+	}
+
+	data = (pg_wchar *) palloc((strlen(encoded_str) + 1) * sizeof(pg_wchar));
+	data_len = pg_mb2wchar_with_len(encoded_str, data, len);
+
+	/* execute the regular expression match */
+	sts = pg_regexec(
+					 &preg,		/* compiled re */
+					 data,		/* target string */
+					 data_len,	/* length of encoded_str */
+					 0,			/* search start */
+					 NULL,		/* rm details */
+					 nmatch,	/* number of match sub re */
+					 pmatch,	/* match result details */
+					 eflags);
+
+	pfree(data);
+
+	if (sts != REG_OKAY)
+	{
+		if (sts != REG_NOMATCH)
+		{
+			char		errMsg[100];
+
+			pg_regerror(sts, &preg, errMsg, sizeof(errMsg));
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("regular expression failed: %s", errMsg)));
+		}
+		return 0;				/* does not match */
+	}
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+
+}
+
+static
+void
+do_pattern_match_finish(void)
+{
+	if (regcache != NULL)
+		pg_regfree(regcache);
+	regcache = NULL;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *
+string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet *string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+			destroyStringInfo(str);
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *
+variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..3142a8bc06 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2dcc2d42da..ee11ee2c8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10664,6 +10664,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 79d5e96021..87591a8d43 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2585,6 +2585,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2644,6 +2649,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2671,6 +2689,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_Dec_21_18_20_04_2024_526)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v25-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v25 5/9] Row pattern recognition patch (executor).
@ 2024-12-21 06:19 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-21 06:19 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1745 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1807 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..2148d4ed1e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -45,9 +46,11 @@
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
+#include "regex/regex.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +162,58 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+} StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+} NavigationInfo;
+
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+} VariablePos;
+
+/*
+ * Regular expression compiled cache for do_pattern_match exists
+ */
+static regex_t *regcache = NULL;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +239,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +251,52 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet *str_set,
+						   VariablePos *variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str, int len);
+static void do_pattern_match_finish(void);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str);
+static StringInfo string_set_get(StringSet *string_set, int index);
+static int	string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos *variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos *variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos *variable_pos);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +873,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +889,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +964,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1034,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1055,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1076,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1151,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1375,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2347,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2460,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2637,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2738,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2929,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2974,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3136,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3498,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3819,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3933,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4018,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4044,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4081,1319 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str;
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			destroyStringInfo(encoded_str);
+
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		destroyStringInfo(encoded_str);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	destroyStringInfo(pattern_str);
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet *str_set, VariablePos *variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chance to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							destroyStringInfo(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data, old->len);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+
+		/*
+		 * If the string is already freeze, we don't need to check it by
+		 * do_pattern_match because it has been ready checked.
+		 */
+		if (s->data[s->len] == FREEZED_CHAR)
+			len = s->len - 1;
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		else if (s->data[s->len] == DISCARD_CHAR)
+			continue;
+
+		else
+			len = do_pattern_match(pattern, s->data, s->len);
+
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	do_pattern_match_finish();
+	return resultlen;
+}
+
+/*
+ * do_pattern_match perform pattern match using pattern against encoded_str
+ * whose length is len bytes (without null terminate).  returns matching
+ * number of rows if matching is succeeded.  Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str, int len)
+{
+	static regex_t preg;
+	int			plen;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+	int			sts;
+	pg_wchar   *data;
+	int			data_len;
+
+
+	/*
+	 * Compile regexp if it does not exist.
+	 */
+	if (regcache == NULL)
+	{
+		/* we need to convert to char to pg_wchar */
+		plen = strlen(pattern);
+		data = (pg_wchar *) palloc((plen + 1) * sizeof(pg_wchar));
+		data_len = pg_mb2wchar_with_len(pattern, data, plen);
+		/* compile re */
+		sts = pg_regcomp(&preg, /* compiled re */
+						 data,	/* target pattern */
+						 data_len,	/* length of pattern */
+						 cflags,	/* compile option */
+						 C_COLLATION_OID	/* collation */
+			);
+		pfree(data);
+
+		if (sts != REG_OKAY)
+		{
+			/* re didn't compile (no need for pg_regfree, if so) */
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("invalid regular expression: %s", pattern)));
+		}
+		regcache = &preg;
+	}
+
+	data = (pg_wchar *) palloc((strlen(encoded_str) + 1) * sizeof(pg_wchar));
+	data_len = pg_mb2wchar_with_len(encoded_str, data, len);
+
+	/* execute the regular expression match */
+	sts = pg_regexec(
+					 &preg,		/* compiled re */
+					 data,		/* target string */
+					 data_len,	/* length of encoded_str */
+					 0,			/* search start */
+					 NULL,		/* rm details */
+					 nmatch,	/* number of match sub re */
+					 pmatch,	/* match result details */
+					 eflags);
+
+	pfree(data);
+
+	if (sts != REG_OKAY)
+	{
+		if (sts != REG_NOMATCH)
+		{
+			char		errMsg[100];
+
+			pg_regerror(sts, &preg, errMsg, sizeof(errMsg));
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("regular expression failed: %s", errMsg)));
+		}
+		return 0;				/* does not match */
+	}
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+
+}
+
+static
+void
+do_pattern_match_finish(void)
+{
+	if (regcache != NULL)
+		pg_regfree(regcache);
+	regcache = NULL;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *
+string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet *string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet *string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet *string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+			destroyStringInfo(str);
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *
+variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos *variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..3142a8bc06 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2dcc2d42da..ee11ee2c8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10664,6 +10664,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 79d5e96021..87591a8d43 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2585,6 +2585,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2644,6 +2649,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2671,6 +2689,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Sat_Dec_21_18_20_04_2024_526)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v25-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v26 5/9] Row pattern recognition patch (executor).
@ 2024-12-30 12:44 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-30 12:44 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1843 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   58 +
 4 files changed, 1933 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..8333021623 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -45,9 +46,11 @@
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
+#include "regex/regex.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +162,33 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+#define	STRSET_FROZEN		(1 << 0)	/* string is frozen */
+#define	STRSET_DISCARDED	(1 << 1)	/* string is scheduled to be discarded */
+#define	STRSET_MATCHED		(1 << 2)	/* string is confirmed to be matched
+										 * with pattern */
+
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+	int		   *info;			/* an array of information bit per StringInfo.
+								 * see above */
+} StringSet;
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+} NavigationInfo;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +214,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +226,60 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(WindowAggState *winstate,
+						   StringSet *input_str_set);
+static StringSet *generate_patterns(StringSet *input_str_set,
+									char *pattern, VariablePos *variable_pos,
+									char tail_pattern_initial);
+static int	add_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+						char c, char *pattern, char tail_pattern_initial,
+						int resultlen);
+static int	freeze_pattern(StringInfo old, int old_info,
+						   StringSet *new_str_set,
+						   char *pattern, int resultlen);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str, int len);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str, int info);
+static StringInfo string_set_get(StringSet *string_set, int index, int *flag);
+static int	string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos *variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos *variable_pos, char initial,
+							   int index);
+static VariablePos *variable_pos_build(WindowAggState *winstate);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +856,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +872,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +947,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1017,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1038,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1059,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos)
+				!= RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto)
+				== RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1134,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1358,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2330,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2443,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2620,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2721,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2912,49 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
+	/*
+	 * Build variable_pos
+	 */
+	if (winstate->defineInitial)
+		winstate->variable_pos = variable_pos_build(winstate);
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2963,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3125,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3487,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3808,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3922,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4007,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4033,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4070,1428 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringSet  *str_set;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			destroyStringInfo(encoded_str);
+
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		int			init_size;
+
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+
+		/*
+		 * makeStringInfo creates initial data size to be 1024 bytes, which is
+		 * too large for us because we only need the initial data as the
+		 * number PATTERN variables (+null terminate), which is usually less
+		 * than 10 bytes. So we reallocate the initial data size as small as
+		 * the number of PATTERN variables.
+		 */
+		encoded_str = makeStringInfo();
+		pfree(encoded_str->data);
+		init_size = list_length(winstate->patternVariableList) + 1;
+		encoded_str->data = (char *) palloc0(init_size);
+		encoded_str->maxlen = init_size;
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str, 0);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		destroyStringInfo(encoded_str);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(winstate, str_set);
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ *
+ * Perform pattern matching using "pattern" against input_str_set. pattern is
+ * a regular expression string derived from PATTERN clause. Note that the
+ * regular expression string is prefixed by '^' and followed by initials
+ * represented in a same way as str_set. str_set is a set of StringInfo. Each
+ * StringInfo has a string comprising initials of pattern variable strings
+ * being true in a row. The initials are one of [a-z], parallel to the order
+ * of variable names in DEFINE clause. Suppose DEFINE has variables START, UP
+ * and DOWN. If PATTERN has START, UP+ and DOWN, then the initials in PATTERN
+ * will be 'a', 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(WindowAggState *winstate, StringSet *input_str_set)
+{
+	char	   *pattern;		/* search regexp pattern */
+	VariablePos *variable_pos;
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *new_str_set;
+	int			new_str_size;
+	int			len;
+	int			info;
+	char		tail_pattern_initial;
+
+	/*
+	 * Set last initial char to tail_pattern_initial if we can apply "tail
+	 * pattern initial optimization".  If the last regexp component in pattern
+	 * is with '+' quatifier, set the initial to tail_pattern_initial.  For
+	 * example if pattern = "ab+", tail_pattern_initial will be 'b'.
+	 * Otherwise, tail_pattern_initial is '\0'.
+	 */
+	pattern = winstate->pattern_str->data;
+	if (pattern[strlen(pattern) - 1] == '+')
+		tail_pattern_initial = pattern[strlen(pattern) - 2];
+	else
+		tail_pattern_initial = '\0';
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	variable_pos = winstate->variable_pos;
+	new_str_set = generate_patterns(input_str_set, pattern, variable_pos,
+									tail_pattern_initial);
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	len = 0;
+	resultlen = 0;
+	set_size = string_set_get_size(input_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index, &info);
+		if (s == NULL)
+			continue;			/* no data */
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		if (info & STRSET_DISCARDED)
+			continue;
+
+		len = do_pattern_match(pattern, s->data, s->len);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * generate_patterns
+ *
+ * Generate all possible pattern variable name initials in 'input_str_set' as
+ * a set of StringInfo and return it.  For example, if we have two rows having
+ * "ab" (row 0) and "ac" (row 1) in 'input str_set', returned StringSet will
+ * have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+ * 'variable_pos' and 'tail_pattern_initial' are used for pruning
+ * optimization.
+ */
+static
+StringSet *
+generate_patterns(StringSet *input_str_set, char *pattern,
+				  VariablePos *variable_pos, char tail_pattern_initial)
+{
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			index;
+	int			set_size;
+	int			old_set_size;
+	int			info;
+	int			resultlen;
+	StringInfo	str;
+	int			i;
+	char	   *p;
+
+	new_str_set = string_set_init();
+	set_size = string_set_get_size(input_str_set);
+	if (set_size == 0)			/* if there's no row in input, return empty
+								 * set */
+		return new_str_set;
+
+	resultlen = 0;
+
+	/*
+	 * Generate initial new_string_set for input row 0.
+	 */
+	str = string_set_get(input_str_set, 0, &info);
+	p = str->data;
+
+	/*
+	 * Loop over each new pattern variable char.
+	 */
+	while (*p)
+	{
+		StringInfo	new = makeStringInfo();
+
+		/* add pattern variable char */
+		appendStringInfoChar(new, *p);
+		/* add new one to string set */
+		string_set_add(new_str_set, new, 0);
+		p++;					/* next pattern variable */
+	}
+
+	/*
+	 * Generate new_string_set for each input row.
+	 */
+	for (index = 1; index < set_size; index++)
+	{
+		/* previous new str set now becomes old str set */
+		old_str_set = new_str_set;
+		new_str_set = string_set_init();	/* create new string set */
+		/* pick up input string */
+		str = string_set_get(input_str_set, index, &info);
+		old_set_size = string_set_get_size(old_str_set);
+
+		/*
+		 * Loop over each row in the previous result set.
+		 */
+		for (i = 0; i < old_set_size; i++)
+		{
+			char		last_old_char;
+			int			old_str_len;
+			int			old_info;
+			StringInfo	old;
+
+			old = string_set_get(old_str_set, i, &old_info);
+			p = old->data;
+			old_str_len = old->len;
+			if (old_str_len > 0)
+				last_old_char = p[old_str_len - 1];
+			else
+				last_old_char = '\0';
+
+			/* Can this old set be discarded? */
+			if (old_info & STRSET_DISCARDED)
+				continue;		/* discard the old string */
+
+			/* Is this old set frozen? */
+			else if (old_info & STRSET_FROZEN)
+			{
+				/* if shorter match. we can discard it */
+				if (old_str_len < resultlen)
+					continue;	/* discard the shorter string */
+
+				/* move the old set to new_str_set */
+				string_set_add(new_str_set, old, old_info);
+				old_str_set->str_set[i] = NULL;
+				continue;
+			}
+
+			/*
+			 * loop over each pattern variable initial char in the input set.
+			 */
+			for (p = str->data; *p; p++)
+			{
+				/*
+				 * Optimization.  Check if the row's pattern variable initial
+				 * character position is greater than or equal to the old
+				 * set's last pattern variable initial character position. For
+				 * example, if the old set's last pattern variable initials
+				 * are "ab", then the new pattern variable initial can be "b"
+				 * or "c" but can not be "a", if the initials in PATTERN is
+				 * something like "a b c" or "a b+ c+" etc.  This optimization
+				 * is possible when we only allow "+" quantifier.
+				 */
+				if (variable_pos_compare(variable_pos, last_old_char, *p))
+
+					/*
+					 * Satisfied the condition. Add new pattern char to
+					 * new_str_set if it looks good.
+					 */
+					resultlen = add_pattern(old, old_info, new_str_set, *p,
+											pattern, tail_pattern_initial, resultlen);
+				else
+
+					/*
+					 * The old_str did not satisfy the condition and it cannot
+					 * be extended further. "Freeze" it.
+					 */
+					resultlen = freeze_pattern(old, old_info,
+											   new_str_set, pattern, resultlen);
+			}
+		}
+		/* we no longer need old string set */
+		string_set_discard(old_str_set);
+	}
+	return new_str_set;
+}
+
+/*
+ * add_pattern
+ *
+ * Make a copy of 'old' (along with 'old_info' flag) and add new pattern char
+ * 'c' to it. Then add it to 'new_str_set'. 'pattern' and
+ * 'tail_pattern_initial' is checked to determine whether the copy is worth to
+ * add to new_str_set or not.  The match length (possibly longer than
+ * 'resultlen') is returned.
+ */
+static
+int
+add_pattern(StringInfo old, int old_info, StringSet *new_str_set, char c,
+			char *pattern, char tail_pattern_initial, int resultlen)
+{
+	StringInfo	new;
+	int			info;
+	int			len;
+
+	/*
+	 * New char in the input row satisfies the condition above.
+	 */
+	new = makeStringInfo();		/* copy source string */
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+
+	/* add pattern variable char */
+	appendStringInfoChar(new, c);
+
+	/*
+	 * Adhoc optimization. If the first letter in the input string is in the
+	 * head and second position and there's no associated quatifier '+', then
+	 * we can dicard the input because there's no chance to expand the string
+	 * further.
+	 *
+	 * For example, pattern "abc" cannot match "aa".
+	 */
+	if (pattern[1] == new->data[0] &&
+		pattern[1] == new->data[1] &&
+		pattern[2] != '+' &&
+		pattern[1] != pattern[2])
+	{
+		destroyStringInfo(new);
+		return resultlen;
+	}
+
+	info = old_info;
+
+	/*
+	 * Check if we can apply "tail pattern initial optimization".  If the last
+	 * regexp component in pattern has '+' quantifier, the component is set to
+	 * the last pattern initial.  For example if pattern is "ab+",
+	 * tail_pattern_initial will become 'b'. Otherwise, tail_pattern_initial
+	 * is '\0'. If the tail pattern initial optimization is possible, we do
+	 * not need to apply regular expression match again.  Suppose we have the
+	 * previous string ended with "b" and the it was confirmed the regular
+	 * expression match, then char 'b' can be added to the string without
+	 * applying the regular expression match again.
+	 */
+	if (c == tail_pattern_initial)	/* tail pattern initial optimization
+									 * possible? */
+	{
+		/*
+		 * Is already confirmed to be matched with pattern?
+		 */
+		if ((info & STRSET_MATCHED) == 0)
+		{
+			/* not confirmed yet */
+			len = do_pattern_match(pattern, new->data, new->len);
+			if (len > 0)
+				info = STRSET_MATCHED;	/* set already confirmed flag */
+		}
+		else
+
+			/*
+			 * already confirmed. Use the string length as the matching length
+			 */
+			len = new->len;
+
+		/* update the longest match length if needed */
+		if (len > resultlen)
+			resultlen = len;
+	}
+
+	/* add new StringInfo to the string set */
+	string_set_add(new_str_set, new, info);
+
+	return resultlen;
+}
+
+/*
+ * freeze_pattern
+ *
+ * "Freeze" 'old' (along with 'old_info' flag) and add it to
+ * 'new_str_set'. Frozen string is known to not be expaned fruther. The frozen
+ * string is check if it satisfies 'pattern'.  If it does not, "discarded"
+ * mark is added. The discarded mark is also added if the match length is
+ * shorter than the current longest match length. The match length (possibly
+ * longer than 'resultlen') is returned.
+ */
+static
+int
+freeze_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+			   char *pattern, int resultlen)
+{
+	int			len;
+	StringInfo	new;
+	int			new_str_size;
+	int			new_index;
+
+	/*
+	 * We are freezing this pattern string.  If the pattern string length is
+	 * shorter than the current longest string length, we don't need to keep
+	 * it.
+	 */
+	if (old->len < resultlen)
+		return resultlen;
+
+	if (old_info & STRSET_MATCHED)
+		/* we don't need to apply pattern match again */
+		len = old->len;
+	else
+	{
+		/* apply pattern match */
+		len = do_pattern_match(pattern, old->data, old->len);
+		if (len <= 0)
+		{
+			/* no match. we can discard it */
+			return resultlen;
+		}
+	}
+	if (len < resultlen)
+	{
+		/* shorter match. we can discard it */
+		return resultlen;
+	}
+
+	/*
+	 * Match length is the longest so far
+	 */
+	resultlen = len;			/* remember the longest match */
+
+	/* freeze the pattern string */
+	new = makeStringInfo();
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+	/* set frozen mark */
+	string_set_add(new_str_set, new, STRSET_FROZEN);
+
+	/*
+	 * Search new_str_set to find out frozen entries that have shorter match
+	 * length. Mark them as "discard" so that they are discarded in the next
+	 * round.
+	 */
+	new_str_size =
+		string_set_get_size(new_str_set) - 1;
+
+	/* loop over new_str_set */
+	for (new_index = 0; new_index < new_str_size; new_index++)
+	{
+		int			info;
+
+		new = string_set_get(new_str_set, new_index, &info);
+
+		/*
+		 * If this is frozen and is not longer than the current longest match
+		 * length, we don't need to keep this.
+		 */
+		if (info & STRSET_FROZEN && new->len < resultlen)
+		{
+			/*
+			 * mark this set to discard in the next round
+			 */
+			info |= STRSET_DISCARDED;
+			new_str_set->info[new_index] = info;
+		}
+	}
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ *
+ * Perform pattern match using 'pattern' against 'encoded_str' whose length is
+ * 'len' bytes (without null terminate).  Returns matching number of rows if
+ * matching is succeeded.  Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str, int len)
+{
+	static regex_t *regcache = NULL;
+	static regex_t preg;
+	static char patbuf[1024];	/* most recent 'pattern' is cached here */
+	int			plen;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+	int			sts;
+	pg_wchar   *data;
+	int			data_len;
+
+	/*
+	 * Compile regexp if cache does not exist or existing cache is not same as
+	 * "pattern".
+	 */
+	if (strcmp(patbuf, pattern))
+	{
+		/*
+		 * The compiled re must live in top memory context because patbuf is
+		 * static data.
+		 */
+		MemoryContext oldContext = MemoryContextSwitchTo(TopMemoryContext);
+
+		if (regcache != NULL)
+			pg_regfree(regcache);	/* free previous re */
+
+		/* we need to convert to char to pg_wchar */
+		plen = strlen(pattern);
+		data = (pg_wchar *) palloc((plen + 1) * sizeof(pg_wchar));
+		data_len = pg_mb2wchar_with_len(pattern, data, plen);
+		/* compile re */
+		sts = pg_regcomp(&preg, /* compiled re */
+						 data,	/* target pattern */
+						 data_len,	/* length of pattern */
+						 cflags,	/* compile option */
+						 C_COLLATION_OID	/* collation */
+			);
+		pfree(data);
+
+		MemoryContextSwitchTo(oldContext);
+
+		if (sts != REG_OKAY)
+		{
+			/* re didn't compile (no need for pg_regfree, if so) */
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("invalid regular expression: %s", pattern)));
+		}
+
+		/* save cache */
+		regcache = &preg;
+		strncpy(patbuf, pattern, sizeof(patbuf));
+	}
+
+	data = (pg_wchar *) palloc((len + 1) * sizeof(pg_wchar));
+	data_len = pg_mb2wchar_with_len(encoded_str, data, len);
+
+	/* execute the regular expression match */
+	sts = pg_regexec(
+					 &preg,		/* compiled re */
+					 data,		/* target string */
+					 data_len,	/* length of encoded_str */
+					 0,			/* search start */
+					 NULL,		/* rm details */
+					 nmatch,	/* number of match sub re */
+					 pmatch,	/* match result details */
+					 eflags);
+
+	pfree(data);
+
+	if (sts != REG_OKAY)
+	{
+		if (sts != REG_NOMATCH)
+		{
+			char		errMsg[100];
+
+			pg_regerror(sts, &preg, errMsg, sizeof(errMsg));
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("regular expression failed: %s", errMsg)));
+		}
+		return 0;				/* does not match */
+	}
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+
+}
+
+/*
+ * evaluate_pattern
+ *
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ *
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ *
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ *
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *
+string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->info = palloc0(set_size * sizeof(int));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ *
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet *string_set, StringInfo str, int info)
+{
+	Size		set_size;
+	Size		old_set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		old_set_size = set_size;
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->info = repalloc0(string_set->info,
+									 old_set_size * sizeof(int),
+									 set_size * sizeof(int));
+		string_set->set_size = set_size;
+	}
+
+	string_set->info[string_set->set_index] = info;
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ *
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet *string_set, int index, int *info)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	*info = string_set->info[index];
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ *
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet *string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+			destroyStringInfo(str);
+	}
+	pfree(string_set->info);
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ *
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *
+variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ *
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ *
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ *
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_build
+ *
+ * Build VariablePos structure and return it.
+ */
+static
+VariablePos *
+variable_pos_build(WindowAggState *winstate)
+{
+	VariablePos *variable_pos;
+	StringInfo	pattern_str;
+	int			initial_index = 0;
+	ListCell   *lc1,
+			   *lc2;
+
+	variable_pos = winstate->variable_pos = variable_pos_init();
+	pattern_str = winstate->pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	return variable_pos;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..3142a8bc06 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2dcc2d42da..ee11ee2c8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10664,6 +10664,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1590b64392..f96782fd6a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2585,6 +2585,37 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
+/*
+ * Allowed PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postions in PATTERN */
+} VariablePos;
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2644,6 +2675,21 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+	VariablePos *variable_pos;	/* list of pattern variable positions */
+	StringInfo	pattern_str;	/* PATTERN initials */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2671,6 +2717,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Dec_30_22_37_18_2024_171)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v26-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v26 5/9] Row pattern recognition patch (executor).
@ 2024-12-30 12:44 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-30 12:44 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1843 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   58 +
 4 files changed, 1933 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..8333021623 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -45,9 +46,11 @@
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
+#include "regex/regex.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +162,33 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+#define	STRSET_FROZEN		(1 << 0)	/* string is frozen */
+#define	STRSET_DISCARDED	(1 << 1)	/* string is scheduled to be discarded */
+#define	STRSET_MATCHED		(1 << 2)	/* string is confirmed to be matched
+										 * with pattern */
+
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+	int		   *info;			/* an array of information bit per StringInfo.
+								 * see above */
+} StringSet;
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+} NavigationInfo;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +214,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +226,60 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(WindowAggState *winstate,
+						   StringSet *input_str_set);
+static StringSet *generate_patterns(StringSet *input_str_set,
+									char *pattern, VariablePos *variable_pos,
+									char tail_pattern_initial);
+static int	add_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+						char c, char *pattern, char tail_pattern_initial,
+						int resultlen);
+static int	freeze_pattern(StringInfo old, int old_info,
+						   StringSet *new_str_set,
+						   char *pattern, int resultlen);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str, int len);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str, int info);
+static StringInfo string_set_get(StringSet *string_set, int index, int *flag);
+static int	string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos *variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos *variable_pos, char initial,
+							   int index);
+static VariablePos *variable_pos_build(WindowAggState *winstate);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +856,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +872,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +947,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1017,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1038,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1059,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos)
+				!= RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto)
+				== RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1134,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1358,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2330,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2443,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2620,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2721,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2912,49 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
+	/*
+	 * Build variable_pos
+	 */
+	if (winstate->defineInitial)
+		winstate->variable_pos = variable_pos_build(winstate);
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2963,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3125,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3487,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3808,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3922,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4007,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4033,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4070,1428 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringSet  *str_set;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			destroyStringInfo(encoded_str);
+
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		int			init_size;
+
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+
+		/*
+		 * makeStringInfo creates initial data size to be 1024 bytes, which is
+		 * too large for us because we only need the initial data as the
+		 * number PATTERN variables (+null terminate), which is usually less
+		 * than 10 bytes. So we reallocate the initial data size as small as
+		 * the number of PATTERN variables.
+		 */
+		encoded_str = makeStringInfo();
+		pfree(encoded_str->data);
+		init_size = list_length(winstate->patternVariableList) + 1;
+		encoded_str->data = (char *) palloc0(init_size);
+		encoded_str->maxlen = init_size;
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str, 0);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		destroyStringInfo(encoded_str);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(winstate, str_set);
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ *
+ * Perform pattern matching using "pattern" against input_str_set. pattern is
+ * a regular expression string derived from PATTERN clause. Note that the
+ * regular expression string is prefixed by '^' and followed by initials
+ * represented in a same way as str_set. str_set is a set of StringInfo. Each
+ * StringInfo has a string comprising initials of pattern variable strings
+ * being true in a row. The initials are one of [a-z], parallel to the order
+ * of variable names in DEFINE clause. Suppose DEFINE has variables START, UP
+ * and DOWN. If PATTERN has START, UP+ and DOWN, then the initials in PATTERN
+ * will be 'a', 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(WindowAggState *winstate, StringSet *input_str_set)
+{
+	char	   *pattern;		/* search regexp pattern */
+	VariablePos *variable_pos;
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *new_str_set;
+	int			new_str_size;
+	int			len;
+	int			info;
+	char		tail_pattern_initial;
+
+	/*
+	 * Set last initial char to tail_pattern_initial if we can apply "tail
+	 * pattern initial optimization".  If the last regexp component in pattern
+	 * is with '+' quatifier, set the initial to tail_pattern_initial.  For
+	 * example if pattern = "ab+", tail_pattern_initial will be 'b'.
+	 * Otherwise, tail_pattern_initial is '\0'.
+	 */
+	pattern = winstate->pattern_str->data;
+	if (pattern[strlen(pattern) - 1] == '+')
+		tail_pattern_initial = pattern[strlen(pattern) - 2];
+	else
+		tail_pattern_initial = '\0';
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	variable_pos = winstate->variable_pos;
+	new_str_set = generate_patterns(input_str_set, pattern, variable_pos,
+									tail_pattern_initial);
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	len = 0;
+	resultlen = 0;
+	set_size = string_set_get_size(input_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index, &info);
+		if (s == NULL)
+			continue;			/* no data */
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		if (info & STRSET_DISCARDED)
+			continue;
+
+		len = do_pattern_match(pattern, s->data, s->len);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * generate_patterns
+ *
+ * Generate all possible pattern variable name initials in 'input_str_set' as
+ * a set of StringInfo and return it.  For example, if we have two rows having
+ * "ab" (row 0) and "ac" (row 1) in 'input str_set', returned StringSet will
+ * have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+ * 'variable_pos' and 'tail_pattern_initial' are used for pruning
+ * optimization.
+ */
+static
+StringSet *
+generate_patterns(StringSet *input_str_set, char *pattern,
+				  VariablePos *variable_pos, char tail_pattern_initial)
+{
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			index;
+	int			set_size;
+	int			old_set_size;
+	int			info;
+	int			resultlen;
+	StringInfo	str;
+	int			i;
+	char	   *p;
+
+	new_str_set = string_set_init();
+	set_size = string_set_get_size(input_str_set);
+	if (set_size == 0)			/* if there's no row in input, return empty
+								 * set */
+		return new_str_set;
+
+	resultlen = 0;
+
+	/*
+	 * Generate initial new_string_set for input row 0.
+	 */
+	str = string_set_get(input_str_set, 0, &info);
+	p = str->data;
+
+	/*
+	 * Loop over each new pattern variable char.
+	 */
+	while (*p)
+	{
+		StringInfo	new = makeStringInfo();
+
+		/* add pattern variable char */
+		appendStringInfoChar(new, *p);
+		/* add new one to string set */
+		string_set_add(new_str_set, new, 0);
+		p++;					/* next pattern variable */
+	}
+
+	/*
+	 * Generate new_string_set for each input row.
+	 */
+	for (index = 1; index < set_size; index++)
+	{
+		/* previous new str set now becomes old str set */
+		old_str_set = new_str_set;
+		new_str_set = string_set_init();	/* create new string set */
+		/* pick up input string */
+		str = string_set_get(input_str_set, index, &info);
+		old_set_size = string_set_get_size(old_str_set);
+
+		/*
+		 * Loop over each row in the previous result set.
+		 */
+		for (i = 0; i < old_set_size; i++)
+		{
+			char		last_old_char;
+			int			old_str_len;
+			int			old_info;
+			StringInfo	old;
+
+			old = string_set_get(old_str_set, i, &old_info);
+			p = old->data;
+			old_str_len = old->len;
+			if (old_str_len > 0)
+				last_old_char = p[old_str_len - 1];
+			else
+				last_old_char = '\0';
+
+			/* Can this old set be discarded? */
+			if (old_info & STRSET_DISCARDED)
+				continue;		/* discard the old string */
+
+			/* Is this old set frozen? */
+			else if (old_info & STRSET_FROZEN)
+			{
+				/* if shorter match. we can discard it */
+				if (old_str_len < resultlen)
+					continue;	/* discard the shorter string */
+
+				/* move the old set to new_str_set */
+				string_set_add(new_str_set, old, old_info);
+				old_str_set->str_set[i] = NULL;
+				continue;
+			}
+
+			/*
+			 * loop over each pattern variable initial char in the input set.
+			 */
+			for (p = str->data; *p; p++)
+			{
+				/*
+				 * Optimization.  Check if the row's pattern variable initial
+				 * character position is greater than or equal to the old
+				 * set's last pattern variable initial character position. For
+				 * example, if the old set's last pattern variable initials
+				 * are "ab", then the new pattern variable initial can be "b"
+				 * or "c" but can not be "a", if the initials in PATTERN is
+				 * something like "a b c" or "a b+ c+" etc.  This optimization
+				 * is possible when we only allow "+" quantifier.
+				 */
+				if (variable_pos_compare(variable_pos, last_old_char, *p))
+
+					/*
+					 * Satisfied the condition. Add new pattern char to
+					 * new_str_set if it looks good.
+					 */
+					resultlen = add_pattern(old, old_info, new_str_set, *p,
+											pattern, tail_pattern_initial, resultlen);
+				else
+
+					/*
+					 * The old_str did not satisfy the condition and it cannot
+					 * be extended further. "Freeze" it.
+					 */
+					resultlen = freeze_pattern(old, old_info,
+											   new_str_set, pattern, resultlen);
+			}
+		}
+		/* we no longer need old string set */
+		string_set_discard(old_str_set);
+	}
+	return new_str_set;
+}
+
+/*
+ * add_pattern
+ *
+ * Make a copy of 'old' (along with 'old_info' flag) and add new pattern char
+ * 'c' to it. Then add it to 'new_str_set'. 'pattern' and
+ * 'tail_pattern_initial' is checked to determine whether the copy is worth to
+ * add to new_str_set or not.  The match length (possibly longer than
+ * 'resultlen') is returned.
+ */
+static
+int
+add_pattern(StringInfo old, int old_info, StringSet *new_str_set, char c,
+			char *pattern, char tail_pattern_initial, int resultlen)
+{
+	StringInfo	new;
+	int			info;
+	int			len;
+
+	/*
+	 * New char in the input row satisfies the condition above.
+	 */
+	new = makeStringInfo();		/* copy source string */
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+
+	/* add pattern variable char */
+	appendStringInfoChar(new, c);
+
+	/*
+	 * Adhoc optimization. If the first letter in the input string is in the
+	 * head and second position and there's no associated quatifier '+', then
+	 * we can dicard the input because there's no chance to expand the string
+	 * further.
+	 *
+	 * For example, pattern "abc" cannot match "aa".
+	 */
+	if (pattern[1] == new->data[0] &&
+		pattern[1] == new->data[1] &&
+		pattern[2] != '+' &&
+		pattern[1] != pattern[2])
+	{
+		destroyStringInfo(new);
+		return resultlen;
+	}
+
+	info = old_info;
+
+	/*
+	 * Check if we can apply "tail pattern initial optimization".  If the last
+	 * regexp component in pattern has '+' quantifier, the component is set to
+	 * the last pattern initial.  For example if pattern is "ab+",
+	 * tail_pattern_initial will become 'b'. Otherwise, tail_pattern_initial
+	 * is '\0'. If the tail pattern initial optimization is possible, we do
+	 * not need to apply regular expression match again.  Suppose we have the
+	 * previous string ended with "b" and the it was confirmed the regular
+	 * expression match, then char 'b' can be added to the string without
+	 * applying the regular expression match again.
+	 */
+	if (c == tail_pattern_initial)	/* tail pattern initial optimization
+									 * possible? */
+	{
+		/*
+		 * Is already confirmed to be matched with pattern?
+		 */
+		if ((info & STRSET_MATCHED) == 0)
+		{
+			/* not confirmed yet */
+			len = do_pattern_match(pattern, new->data, new->len);
+			if (len > 0)
+				info = STRSET_MATCHED;	/* set already confirmed flag */
+		}
+		else
+
+			/*
+			 * already confirmed. Use the string length as the matching length
+			 */
+			len = new->len;
+
+		/* update the longest match length if needed */
+		if (len > resultlen)
+			resultlen = len;
+	}
+
+	/* add new StringInfo to the string set */
+	string_set_add(new_str_set, new, info);
+
+	return resultlen;
+}
+
+/*
+ * freeze_pattern
+ *
+ * "Freeze" 'old' (along with 'old_info' flag) and add it to
+ * 'new_str_set'. Frozen string is known to not be expaned fruther. The frozen
+ * string is check if it satisfies 'pattern'.  If it does not, "discarded"
+ * mark is added. The discarded mark is also added if the match length is
+ * shorter than the current longest match length. The match length (possibly
+ * longer than 'resultlen') is returned.
+ */
+static
+int
+freeze_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+			   char *pattern, int resultlen)
+{
+	int			len;
+	StringInfo	new;
+	int			new_str_size;
+	int			new_index;
+
+	/*
+	 * We are freezing this pattern string.  If the pattern string length is
+	 * shorter than the current longest string length, we don't need to keep
+	 * it.
+	 */
+	if (old->len < resultlen)
+		return resultlen;
+
+	if (old_info & STRSET_MATCHED)
+		/* we don't need to apply pattern match again */
+		len = old->len;
+	else
+	{
+		/* apply pattern match */
+		len = do_pattern_match(pattern, old->data, old->len);
+		if (len <= 0)
+		{
+			/* no match. we can discard it */
+			return resultlen;
+		}
+	}
+	if (len < resultlen)
+	{
+		/* shorter match. we can discard it */
+		return resultlen;
+	}
+
+	/*
+	 * Match length is the longest so far
+	 */
+	resultlen = len;			/* remember the longest match */
+
+	/* freeze the pattern string */
+	new = makeStringInfo();
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+	/* set frozen mark */
+	string_set_add(new_str_set, new, STRSET_FROZEN);
+
+	/*
+	 * Search new_str_set to find out frozen entries that have shorter match
+	 * length. Mark them as "discard" so that they are discarded in the next
+	 * round.
+	 */
+	new_str_size =
+		string_set_get_size(new_str_set) - 1;
+
+	/* loop over new_str_set */
+	for (new_index = 0; new_index < new_str_size; new_index++)
+	{
+		int			info;
+
+		new = string_set_get(new_str_set, new_index, &info);
+
+		/*
+		 * If this is frozen and is not longer than the current longest match
+		 * length, we don't need to keep this.
+		 */
+		if (info & STRSET_FROZEN && new->len < resultlen)
+		{
+			/*
+			 * mark this set to discard in the next round
+			 */
+			info |= STRSET_DISCARDED;
+			new_str_set->info[new_index] = info;
+		}
+	}
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ *
+ * Perform pattern match using 'pattern' against 'encoded_str' whose length is
+ * 'len' bytes (without null terminate).  Returns matching number of rows if
+ * matching is succeeded.  Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str, int len)
+{
+	static regex_t *regcache = NULL;
+	static regex_t preg;
+	static char patbuf[1024];	/* most recent 'pattern' is cached here */
+	int			plen;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+	int			sts;
+	pg_wchar   *data;
+	int			data_len;
+
+	/*
+	 * Compile regexp if cache does not exist or existing cache is not same as
+	 * "pattern".
+	 */
+	if (strcmp(patbuf, pattern))
+	{
+		/*
+		 * The compiled re must live in top memory context because patbuf is
+		 * static data.
+		 */
+		MemoryContext oldContext = MemoryContextSwitchTo(TopMemoryContext);
+
+		if (regcache != NULL)
+			pg_regfree(regcache);	/* free previous re */
+
+		/* we need to convert to char to pg_wchar */
+		plen = strlen(pattern);
+		data = (pg_wchar *) palloc((plen + 1) * sizeof(pg_wchar));
+		data_len = pg_mb2wchar_with_len(pattern, data, plen);
+		/* compile re */
+		sts = pg_regcomp(&preg, /* compiled re */
+						 data,	/* target pattern */
+						 data_len,	/* length of pattern */
+						 cflags,	/* compile option */
+						 C_COLLATION_OID	/* collation */
+			);
+		pfree(data);
+
+		MemoryContextSwitchTo(oldContext);
+
+		if (sts != REG_OKAY)
+		{
+			/* re didn't compile (no need for pg_regfree, if so) */
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("invalid regular expression: %s", pattern)));
+		}
+
+		/* save cache */
+		regcache = &preg;
+		strncpy(patbuf, pattern, sizeof(patbuf));
+	}
+
+	data = (pg_wchar *) palloc((len + 1) * sizeof(pg_wchar));
+	data_len = pg_mb2wchar_with_len(encoded_str, data, len);
+
+	/* execute the regular expression match */
+	sts = pg_regexec(
+					 &preg,		/* compiled re */
+					 data,		/* target string */
+					 data_len,	/* length of encoded_str */
+					 0,			/* search start */
+					 NULL,		/* rm details */
+					 nmatch,	/* number of match sub re */
+					 pmatch,	/* match result details */
+					 eflags);
+
+	pfree(data);
+
+	if (sts != REG_OKAY)
+	{
+		if (sts != REG_NOMATCH)
+		{
+			char		errMsg[100];
+
+			pg_regerror(sts, &preg, errMsg, sizeof(errMsg));
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("regular expression failed: %s", errMsg)));
+		}
+		return 0;				/* does not match */
+	}
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+
+}
+
+/*
+ * evaluate_pattern
+ *
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ *
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ *
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ *
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *
+string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->info = palloc0(set_size * sizeof(int));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ *
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet *string_set, StringInfo str, int info)
+{
+	Size		set_size;
+	Size		old_set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		old_set_size = set_size;
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->info = repalloc0(string_set->info,
+									 old_set_size * sizeof(int),
+									 set_size * sizeof(int));
+		string_set->set_size = set_size;
+	}
+
+	string_set->info[string_set->set_index] = info;
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ *
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet *string_set, int index, int *info)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	*info = string_set->info[index];
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ *
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet *string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+			destroyStringInfo(str);
+	}
+	pfree(string_set->info);
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ *
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *
+variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ *
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ *
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ *
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_build
+ *
+ * Build VariablePos structure and return it.
+ */
+static
+VariablePos *
+variable_pos_build(WindowAggState *winstate)
+{
+	VariablePos *variable_pos;
+	StringInfo	pattern_str;
+	int			initial_index = 0;
+	ListCell   *lc1,
+			   *lc2;
+
+	variable_pos = winstate->variable_pos = variable_pos_init();
+	pattern_str = winstate->pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	return variable_pos;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..3142a8bc06 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2dcc2d42da..ee11ee2c8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10664,6 +10664,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1590b64392..f96782fd6a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2585,6 +2585,37 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
+/*
+ * Allowed PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postions in PATTERN */
+} VariablePos;
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2644,6 +2675,21 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+	VariablePos *variable_pos;	/* list of pattern variable positions */
+	StringInfo	pattern_str;	/* PATTERN initials */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2671,6 +2717,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Dec_30_22_37_18_2024_171)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v26-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v26 5/9] Row pattern recognition patch (executor).
@ 2024-12-30 12:44 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-30 12:44 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1843 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   58 +
 4 files changed, 1933 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..8333021623 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -45,9 +46,11 @@
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
+#include "regex/regex.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +162,33 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+#define	STRSET_FROZEN		(1 << 0)	/* string is frozen */
+#define	STRSET_DISCARDED	(1 << 1)	/* string is scheduled to be discarded */
+#define	STRSET_MATCHED		(1 << 2)	/* string is confirmed to be matched
+										 * with pattern */
+
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+	int		   *info;			/* an array of information bit per StringInfo.
+								 * see above */
+} StringSet;
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+} NavigationInfo;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +214,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +226,60 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(WindowAggState *winstate,
+						   StringSet *input_str_set);
+static StringSet *generate_patterns(StringSet *input_str_set,
+									char *pattern, VariablePos *variable_pos,
+									char tail_pattern_initial);
+static int	add_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+						char c, char *pattern, char tail_pattern_initial,
+						int resultlen);
+static int	freeze_pattern(StringInfo old, int old_info,
+						   StringSet *new_str_set,
+						   char *pattern, int resultlen);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str, int len);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str, int info);
+static StringInfo string_set_get(StringSet *string_set, int index, int *flag);
+static int	string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos *variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos *variable_pos, char initial,
+							   int index);
+static VariablePos *variable_pos_build(WindowAggState *winstate);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +856,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +872,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +947,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1017,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1038,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1059,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos)
+				!= RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto)
+				== RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1134,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1358,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2330,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2443,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2620,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2721,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2912,49 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
+	/*
+	 * Build variable_pos
+	 */
+	if (winstate->defineInitial)
+		winstate->variable_pos = variable_pos_build(winstate);
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2963,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3125,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3487,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3808,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3922,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4007,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4033,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4070,1428 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringSet  *str_set;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			destroyStringInfo(encoded_str);
+
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		int			init_size;
+
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+
+		/*
+		 * makeStringInfo creates initial data size to be 1024 bytes, which is
+		 * too large for us because we only need the initial data as the
+		 * number PATTERN variables (+null terminate), which is usually less
+		 * than 10 bytes. So we reallocate the initial data size as small as
+		 * the number of PATTERN variables.
+		 */
+		encoded_str = makeStringInfo();
+		pfree(encoded_str->data);
+		init_size = list_length(winstate->patternVariableList) + 1;
+		encoded_str->data = (char *) palloc0(init_size);
+		encoded_str->maxlen = init_size;
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str, 0);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		destroyStringInfo(encoded_str);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(winstate, str_set);
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ *
+ * Perform pattern matching using "pattern" against input_str_set. pattern is
+ * a regular expression string derived from PATTERN clause. Note that the
+ * regular expression string is prefixed by '^' and followed by initials
+ * represented in a same way as str_set. str_set is a set of StringInfo. Each
+ * StringInfo has a string comprising initials of pattern variable strings
+ * being true in a row. The initials are one of [a-z], parallel to the order
+ * of variable names in DEFINE clause. Suppose DEFINE has variables START, UP
+ * and DOWN. If PATTERN has START, UP+ and DOWN, then the initials in PATTERN
+ * will be 'a', 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(WindowAggState *winstate, StringSet *input_str_set)
+{
+	char	   *pattern;		/* search regexp pattern */
+	VariablePos *variable_pos;
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *new_str_set;
+	int			new_str_size;
+	int			len;
+	int			info;
+	char		tail_pattern_initial;
+
+	/*
+	 * Set last initial char to tail_pattern_initial if we can apply "tail
+	 * pattern initial optimization".  If the last regexp component in pattern
+	 * is with '+' quatifier, set the initial to tail_pattern_initial.  For
+	 * example if pattern = "ab+", tail_pattern_initial will be 'b'.
+	 * Otherwise, tail_pattern_initial is '\0'.
+	 */
+	pattern = winstate->pattern_str->data;
+	if (pattern[strlen(pattern) - 1] == '+')
+		tail_pattern_initial = pattern[strlen(pattern) - 2];
+	else
+		tail_pattern_initial = '\0';
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	variable_pos = winstate->variable_pos;
+	new_str_set = generate_patterns(input_str_set, pattern, variable_pos,
+									tail_pattern_initial);
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	len = 0;
+	resultlen = 0;
+	set_size = string_set_get_size(input_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index, &info);
+		if (s == NULL)
+			continue;			/* no data */
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		if (info & STRSET_DISCARDED)
+			continue;
+
+		len = do_pattern_match(pattern, s->data, s->len);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * generate_patterns
+ *
+ * Generate all possible pattern variable name initials in 'input_str_set' as
+ * a set of StringInfo and return it.  For example, if we have two rows having
+ * "ab" (row 0) and "ac" (row 1) in 'input str_set', returned StringSet will
+ * have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+ * 'variable_pos' and 'tail_pattern_initial' are used for pruning
+ * optimization.
+ */
+static
+StringSet *
+generate_patterns(StringSet *input_str_set, char *pattern,
+				  VariablePos *variable_pos, char tail_pattern_initial)
+{
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			index;
+	int			set_size;
+	int			old_set_size;
+	int			info;
+	int			resultlen;
+	StringInfo	str;
+	int			i;
+	char	   *p;
+
+	new_str_set = string_set_init();
+	set_size = string_set_get_size(input_str_set);
+	if (set_size == 0)			/* if there's no row in input, return empty
+								 * set */
+		return new_str_set;
+
+	resultlen = 0;
+
+	/*
+	 * Generate initial new_string_set for input row 0.
+	 */
+	str = string_set_get(input_str_set, 0, &info);
+	p = str->data;
+
+	/*
+	 * Loop over each new pattern variable char.
+	 */
+	while (*p)
+	{
+		StringInfo	new = makeStringInfo();
+
+		/* add pattern variable char */
+		appendStringInfoChar(new, *p);
+		/* add new one to string set */
+		string_set_add(new_str_set, new, 0);
+		p++;					/* next pattern variable */
+	}
+
+	/*
+	 * Generate new_string_set for each input row.
+	 */
+	for (index = 1; index < set_size; index++)
+	{
+		/* previous new str set now becomes old str set */
+		old_str_set = new_str_set;
+		new_str_set = string_set_init();	/* create new string set */
+		/* pick up input string */
+		str = string_set_get(input_str_set, index, &info);
+		old_set_size = string_set_get_size(old_str_set);
+
+		/*
+		 * Loop over each row in the previous result set.
+		 */
+		for (i = 0; i < old_set_size; i++)
+		{
+			char		last_old_char;
+			int			old_str_len;
+			int			old_info;
+			StringInfo	old;
+
+			old = string_set_get(old_str_set, i, &old_info);
+			p = old->data;
+			old_str_len = old->len;
+			if (old_str_len > 0)
+				last_old_char = p[old_str_len - 1];
+			else
+				last_old_char = '\0';
+
+			/* Can this old set be discarded? */
+			if (old_info & STRSET_DISCARDED)
+				continue;		/* discard the old string */
+
+			/* Is this old set frozen? */
+			else if (old_info & STRSET_FROZEN)
+			{
+				/* if shorter match. we can discard it */
+				if (old_str_len < resultlen)
+					continue;	/* discard the shorter string */
+
+				/* move the old set to new_str_set */
+				string_set_add(new_str_set, old, old_info);
+				old_str_set->str_set[i] = NULL;
+				continue;
+			}
+
+			/*
+			 * loop over each pattern variable initial char in the input set.
+			 */
+			for (p = str->data; *p; p++)
+			{
+				/*
+				 * Optimization.  Check if the row's pattern variable initial
+				 * character position is greater than or equal to the old
+				 * set's last pattern variable initial character position. For
+				 * example, if the old set's last pattern variable initials
+				 * are "ab", then the new pattern variable initial can be "b"
+				 * or "c" but can not be "a", if the initials in PATTERN is
+				 * something like "a b c" or "a b+ c+" etc.  This optimization
+				 * is possible when we only allow "+" quantifier.
+				 */
+				if (variable_pos_compare(variable_pos, last_old_char, *p))
+
+					/*
+					 * Satisfied the condition. Add new pattern char to
+					 * new_str_set if it looks good.
+					 */
+					resultlen = add_pattern(old, old_info, new_str_set, *p,
+											pattern, tail_pattern_initial, resultlen);
+				else
+
+					/*
+					 * The old_str did not satisfy the condition and it cannot
+					 * be extended further. "Freeze" it.
+					 */
+					resultlen = freeze_pattern(old, old_info,
+											   new_str_set, pattern, resultlen);
+			}
+		}
+		/* we no longer need old string set */
+		string_set_discard(old_str_set);
+	}
+	return new_str_set;
+}
+
+/*
+ * add_pattern
+ *
+ * Make a copy of 'old' (along with 'old_info' flag) and add new pattern char
+ * 'c' to it. Then add it to 'new_str_set'. 'pattern' and
+ * 'tail_pattern_initial' is checked to determine whether the copy is worth to
+ * add to new_str_set or not.  The match length (possibly longer than
+ * 'resultlen') is returned.
+ */
+static
+int
+add_pattern(StringInfo old, int old_info, StringSet *new_str_set, char c,
+			char *pattern, char tail_pattern_initial, int resultlen)
+{
+	StringInfo	new;
+	int			info;
+	int			len;
+
+	/*
+	 * New char in the input row satisfies the condition above.
+	 */
+	new = makeStringInfo();		/* copy source string */
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+
+	/* add pattern variable char */
+	appendStringInfoChar(new, c);
+
+	/*
+	 * Adhoc optimization. If the first letter in the input string is in the
+	 * head and second position and there's no associated quatifier '+', then
+	 * we can dicard the input because there's no chance to expand the string
+	 * further.
+	 *
+	 * For example, pattern "abc" cannot match "aa".
+	 */
+	if (pattern[1] == new->data[0] &&
+		pattern[1] == new->data[1] &&
+		pattern[2] != '+' &&
+		pattern[1] != pattern[2])
+	{
+		destroyStringInfo(new);
+		return resultlen;
+	}
+
+	info = old_info;
+
+	/*
+	 * Check if we can apply "tail pattern initial optimization".  If the last
+	 * regexp component in pattern has '+' quantifier, the component is set to
+	 * the last pattern initial.  For example if pattern is "ab+",
+	 * tail_pattern_initial will become 'b'. Otherwise, tail_pattern_initial
+	 * is '\0'. If the tail pattern initial optimization is possible, we do
+	 * not need to apply regular expression match again.  Suppose we have the
+	 * previous string ended with "b" and the it was confirmed the regular
+	 * expression match, then char 'b' can be added to the string without
+	 * applying the regular expression match again.
+	 */
+	if (c == tail_pattern_initial)	/* tail pattern initial optimization
+									 * possible? */
+	{
+		/*
+		 * Is already confirmed to be matched with pattern?
+		 */
+		if ((info & STRSET_MATCHED) == 0)
+		{
+			/* not confirmed yet */
+			len = do_pattern_match(pattern, new->data, new->len);
+			if (len > 0)
+				info = STRSET_MATCHED;	/* set already confirmed flag */
+		}
+		else
+
+			/*
+			 * already confirmed. Use the string length as the matching length
+			 */
+			len = new->len;
+
+		/* update the longest match length if needed */
+		if (len > resultlen)
+			resultlen = len;
+	}
+
+	/* add new StringInfo to the string set */
+	string_set_add(new_str_set, new, info);
+
+	return resultlen;
+}
+
+/*
+ * freeze_pattern
+ *
+ * "Freeze" 'old' (along with 'old_info' flag) and add it to
+ * 'new_str_set'. Frozen string is known to not be expaned fruther. The frozen
+ * string is check if it satisfies 'pattern'.  If it does not, "discarded"
+ * mark is added. The discarded mark is also added if the match length is
+ * shorter than the current longest match length. The match length (possibly
+ * longer than 'resultlen') is returned.
+ */
+static
+int
+freeze_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+			   char *pattern, int resultlen)
+{
+	int			len;
+	StringInfo	new;
+	int			new_str_size;
+	int			new_index;
+
+	/*
+	 * We are freezing this pattern string.  If the pattern string length is
+	 * shorter than the current longest string length, we don't need to keep
+	 * it.
+	 */
+	if (old->len < resultlen)
+		return resultlen;
+
+	if (old_info & STRSET_MATCHED)
+		/* we don't need to apply pattern match again */
+		len = old->len;
+	else
+	{
+		/* apply pattern match */
+		len = do_pattern_match(pattern, old->data, old->len);
+		if (len <= 0)
+		{
+			/* no match. we can discard it */
+			return resultlen;
+		}
+	}
+	if (len < resultlen)
+	{
+		/* shorter match. we can discard it */
+		return resultlen;
+	}
+
+	/*
+	 * Match length is the longest so far
+	 */
+	resultlen = len;			/* remember the longest match */
+
+	/* freeze the pattern string */
+	new = makeStringInfo();
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+	/* set frozen mark */
+	string_set_add(new_str_set, new, STRSET_FROZEN);
+
+	/*
+	 * Search new_str_set to find out frozen entries that have shorter match
+	 * length. Mark them as "discard" so that they are discarded in the next
+	 * round.
+	 */
+	new_str_size =
+		string_set_get_size(new_str_set) - 1;
+
+	/* loop over new_str_set */
+	for (new_index = 0; new_index < new_str_size; new_index++)
+	{
+		int			info;
+
+		new = string_set_get(new_str_set, new_index, &info);
+
+		/*
+		 * If this is frozen and is not longer than the current longest match
+		 * length, we don't need to keep this.
+		 */
+		if (info & STRSET_FROZEN && new->len < resultlen)
+		{
+			/*
+			 * mark this set to discard in the next round
+			 */
+			info |= STRSET_DISCARDED;
+			new_str_set->info[new_index] = info;
+		}
+	}
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ *
+ * Perform pattern match using 'pattern' against 'encoded_str' whose length is
+ * 'len' bytes (without null terminate).  Returns matching number of rows if
+ * matching is succeeded.  Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str, int len)
+{
+	static regex_t *regcache = NULL;
+	static regex_t preg;
+	static char patbuf[1024];	/* most recent 'pattern' is cached here */
+	int			plen;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+	int			sts;
+	pg_wchar   *data;
+	int			data_len;
+
+	/*
+	 * Compile regexp if cache does not exist or existing cache is not same as
+	 * "pattern".
+	 */
+	if (strcmp(patbuf, pattern))
+	{
+		/*
+		 * The compiled re must live in top memory context because patbuf is
+		 * static data.
+		 */
+		MemoryContext oldContext = MemoryContextSwitchTo(TopMemoryContext);
+
+		if (regcache != NULL)
+			pg_regfree(regcache);	/* free previous re */
+
+		/* we need to convert to char to pg_wchar */
+		plen = strlen(pattern);
+		data = (pg_wchar *) palloc((plen + 1) * sizeof(pg_wchar));
+		data_len = pg_mb2wchar_with_len(pattern, data, plen);
+		/* compile re */
+		sts = pg_regcomp(&preg, /* compiled re */
+						 data,	/* target pattern */
+						 data_len,	/* length of pattern */
+						 cflags,	/* compile option */
+						 C_COLLATION_OID	/* collation */
+			);
+		pfree(data);
+
+		MemoryContextSwitchTo(oldContext);
+
+		if (sts != REG_OKAY)
+		{
+			/* re didn't compile (no need for pg_regfree, if so) */
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("invalid regular expression: %s", pattern)));
+		}
+
+		/* save cache */
+		regcache = &preg;
+		strncpy(patbuf, pattern, sizeof(patbuf));
+	}
+
+	data = (pg_wchar *) palloc((len + 1) * sizeof(pg_wchar));
+	data_len = pg_mb2wchar_with_len(encoded_str, data, len);
+
+	/* execute the regular expression match */
+	sts = pg_regexec(
+					 &preg,		/* compiled re */
+					 data,		/* target string */
+					 data_len,	/* length of encoded_str */
+					 0,			/* search start */
+					 NULL,		/* rm details */
+					 nmatch,	/* number of match sub re */
+					 pmatch,	/* match result details */
+					 eflags);
+
+	pfree(data);
+
+	if (sts != REG_OKAY)
+	{
+		if (sts != REG_NOMATCH)
+		{
+			char		errMsg[100];
+
+			pg_regerror(sts, &preg, errMsg, sizeof(errMsg));
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("regular expression failed: %s", errMsg)));
+		}
+		return 0;				/* does not match */
+	}
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+
+}
+
+/*
+ * evaluate_pattern
+ *
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ *
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ *
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ *
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *
+string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->info = palloc0(set_size * sizeof(int));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ *
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet *string_set, StringInfo str, int info)
+{
+	Size		set_size;
+	Size		old_set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		old_set_size = set_size;
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->info = repalloc0(string_set->info,
+									 old_set_size * sizeof(int),
+									 set_size * sizeof(int));
+		string_set->set_size = set_size;
+	}
+
+	string_set->info[string_set->set_index] = info;
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ *
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet *string_set, int index, int *info)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	*info = string_set->info[index];
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ *
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet *string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+			destroyStringInfo(str);
+	}
+	pfree(string_set->info);
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ *
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *
+variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ *
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ *
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ *
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_build
+ *
+ * Build VariablePos structure and return it.
+ */
+static
+VariablePos *
+variable_pos_build(WindowAggState *winstate)
+{
+	VariablePos *variable_pos;
+	StringInfo	pattern_str;
+	int			initial_index = 0;
+	ListCell   *lc1,
+			   *lc2;
+
+	variable_pos = winstate->variable_pos = variable_pos_init();
+	pattern_str = winstate->pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	return variable_pos;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..3142a8bc06 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2dcc2d42da..ee11ee2c8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10664,6 +10664,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1590b64392..f96782fd6a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2585,6 +2585,37 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
+/*
+ * Allowed PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postions in PATTERN */
+} VariablePos;
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2644,6 +2675,21 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+	VariablePos *variable_pos;	/* list of pattern variable positions */
+	StringInfo	pattern_str;	/* PATTERN initials */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2671,6 +2717,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Mon_Dec_30_22_37_18_2024_171)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v26-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v27 5/9] Row pattern recognition patch (executor).
@ 2024-12-30 23:53 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-30 23:53 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1845 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   58 +
 4 files changed, 1935 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..2a06676572 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -45,9 +46,11 @@
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
+#include "regex/regex.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +162,33 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+#define	STRSET_FROZEN		(1 << 0)	/* string is frozen */
+#define	STRSET_DISCARDED	(1 << 1)	/* string is scheduled to be discarded */
+#define	STRSET_MATCHED		(1 << 2)	/* string is confirmed to be matched
+										 * with pattern */
+
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+	int		   *info;			/* an array of information bit per StringInfo.
+								 * see above */
+} StringSet;
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+} NavigationInfo;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +214,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +226,60 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(WindowAggState *winstate,
+						   StringSet *input_str_set);
+static StringSet *generate_patterns(StringSet *input_str_set,
+									char *pattern, VariablePos *variable_pos,
+									char tail_pattern_initial);
+static int	add_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+						char c, char *pattern, char tail_pattern_initial,
+						int resultlen);
+static int	freeze_pattern(StringInfo old, int old_info,
+						   StringSet *new_str_set,
+						   char *pattern, int resultlen);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str, int len);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str, int info);
+static StringInfo string_set_get(StringSet *string_set, int index, int *flag);
+static int	string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos *variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos *variable_pos, char initial,
+							   int index);
+static VariablePos *variable_pos_build(WindowAggState *winstate);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +856,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +872,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +947,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1017,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1038,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1059,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos)
+				!= RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto)
+				== RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1134,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1358,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2330,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2443,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2620,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2721,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2912,49 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
+	/*
+	 * Build variable_pos
+	 */
+	if (winstate->defineInitial)
+		winstate->variable_pos = variable_pos_build(winstate);
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2963,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3125,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3487,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3808,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3922,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4007,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4033,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4070,1430 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringSet  *str_set;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			destroyStringInfo(encoded_str);
+
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		int			init_size;
+
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+
+		/*
+		 * makeStringInfo creates initial data size to be 1024 bytes, which is
+		 * too large for us because we only need the initial data as the
+		 * number PATTERN variables (+null terminate), which is usually less
+		 * than 10 bytes. So we reallocate the initial data size as small as
+		 * the number of PATTERN variables.
+		 */
+		encoded_str = makeStringInfo();
+		pfree(encoded_str->data);
+		init_size = list_length(winstate->patternVariableList) + 1;
+		encoded_str->data = (char *) palloc0(init_size);
+		encoded_str->maxlen = init_size;
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str, 0);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		destroyStringInfo(encoded_str);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(winstate, str_set);
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ *
+ * Perform pattern matching using "pattern" against input_str_set. pattern is
+ * a regular expression string derived from PATTERN clause. Note that the
+ * regular expression string is prefixed by '^' and followed by initials
+ * represented in a same way as str_set. str_set is a set of StringInfo. Each
+ * StringInfo has a string comprising initials of pattern variable strings
+ * being true in a row. The initials are one of [a-z], parallel to the order
+ * of variable names in DEFINE clause. Suppose DEFINE has variables START, UP
+ * and DOWN. If PATTERN has START, UP+ and DOWN, then the initials in PATTERN
+ * will be 'a', 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(WindowAggState *winstate, StringSet *input_str_set)
+{
+	char	   *pattern;		/* search regexp pattern */
+	VariablePos *variable_pos;
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *new_str_set;
+	int			new_str_size;
+	int			len;
+	int			info;
+	char		tail_pattern_initial;
+
+	/*
+	 * Set last initial char to tail_pattern_initial if we can apply "tail
+	 * pattern initial optimization".  If the last regexp component in pattern
+	 * is with '+' quatifier, set the initial to tail_pattern_initial.  For
+	 * example if pattern = "ab+", tail_pattern_initial will be 'b'.
+	 * Otherwise, tail_pattern_initial is '\0'.
+	 */
+	pattern = winstate->pattern_str->data;
+	if (pattern[strlen(pattern) - 1] == '+')
+		tail_pattern_initial = pattern[strlen(pattern) - 2];
+	else
+		tail_pattern_initial = '\0';
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	variable_pos = winstate->variable_pos;
+	new_str_set = generate_patterns(input_str_set, pattern, variable_pos,
+									tail_pattern_initial);
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	len = 0;
+	resultlen = 0;
+	set_size = string_set_get_size(input_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index, &info);
+		if (s == NULL)
+			continue;			/* no data */
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		if (info & STRSET_DISCARDED)
+			continue;
+
+		len = do_pattern_match(pattern, s->data, s->len);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * generate_patterns
+ *
+ * Generate all possible pattern variable name initials in 'input_str_set' as
+ * a set of StringInfo and return it.  For example, if we have two rows having
+ * "ab" (row 0) and "ac" (row 1) in 'input str_set', returned StringSet will
+ * have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+ * 'variable_pos' and 'tail_pattern_initial' are used for pruning
+ * optimization.
+ */
+static
+StringSet *
+generate_patterns(StringSet *input_str_set, char *pattern,
+				  VariablePos *variable_pos, char tail_pattern_initial)
+{
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			index;
+	int			set_size;
+	int			old_set_size;
+	int			info;
+	int			resultlen;
+	StringInfo	str;
+	int			i;
+	char	   *p;
+
+	new_str_set = string_set_init();
+	set_size = string_set_get_size(input_str_set);
+	if (set_size == 0)			/* if there's no row in input, return empty
+								 * set */
+		return new_str_set;
+
+	resultlen = 0;
+
+	/*
+	 * Generate initial new_string_set for input row 0.
+	 */
+	str = string_set_get(input_str_set, 0, &info);
+	p = str->data;
+
+	/*
+	 * Loop over each new pattern variable char.
+	 */
+	while (*p)
+	{
+		StringInfo	new = makeStringInfo();
+
+		/* add pattern variable char */
+		appendStringInfoChar(new, *p);
+		/* add new one to string set */
+		string_set_add(new_str_set, new, 0);
+		p++;					/* next pattern variable */
+	}
+
+	/*
+	 * Generate new_string_set for each input row.
+	 */
+	for (index = 1; index < set_size; index++)
+	{
+		/* previous new str set now becomes old str set */
+		old_str_set = new_str_set;
+		new_str_set = string_set_init();	/* create new string set */
+		/* pick up input string */
+		str = string_set_get(input_str_set, index, &info);
+		old_set_size = string_set_get_size(old_str_set);
+
+		/*
+		 * Loop over each row in the previous result set.
+		 */
+		for (i = 0; i < old_set_size; i++)
+		{
+			char		last_old_char;
+			int			old_str_len;
+			int			old_info;
+			StringInfo	old;
+
+			old = string_set_get(old_str_set, i, &old_info);
+			p = old->data;
+			old_str_len = old->len;
+			if (old_str_len > 0)
+				last_old_char = p[old_str_len - 1];
+			else
+				last_old_char = '\0';
+
+			/* Can this old set be discarded? */
+			if (old_info & STRSET_DISCARDED)
+				continue;		/* discard the old string */
+
+			/* Is this old set frozen? */
+			else if (old_info & STRSET_FROZEN)
+			{
+				/* if shorter match. we can discard it */
+				if (old_str_len < resultlen)
+					continue;	/* discard the shorter string */
+
+				/* move the old set to new_str_set */
+				string_set_add(new_str_set, old, old_info);
+				old_str_set->str_set[i] = NULL;
+				continue;
+			}
+
+			/*
+			 * loop over each pattern variable initial char in the input set.
+			 */
+			for (p = str->data; *p; p++)
+			{
+				/*
+				 * Optimization.  Check if the row's pattern variable initial
+				 * character position is greater than or equal to the old
+				 * set's last pattern variable initial character position. For
+				 * example, if the old set's last pattern variable initials
+				 * are "ab", then the new pattern variable initial can be "b"
+				 * or "c" but can not be "a", if the initials in PATTERN is
+				 * something like "a b c" or "a b+ c+" etc.  This optimization
+				 * is possible when we only allow "+" quantifier.
+				 */
+				if (variable_pos_compare(variable_pos, last_old_char, *p))
+
+					/*
+					 * Satisfied the condition. Add new pattern char to
+					 * new_str_set if it looks good.
+					 */
+					resultlen = add_pattern(old, old_info, new_str_set, *p,
+											pattern, tail_pattern_initial, resultlen);
+				else
+
+					/*
+					 * The old_str did not satisfy the condition and it cannot
+					 * be extended further. "Freeze" it.
+					 */
+					resultlen = freeze_pattern(old, old_info,
+											   new_str_set, pattern, resultlen);
+			}
+		}
+		/* we no longer need old string set */
+		string_set_discard(old_str_set);
+	}
+	return new_str_set;
+}
+
+/*
+ * add_pattern
+ *
+ * Make a copy of 'old' (along with 'old_info' flag) and add new pattern char
+ * 'c' to it. Then add it to 'new_str_set'. 'pattern' and
+ * 'tail_pattern_initial' is checked to determine whether the copy is worth to
+ * add to new_str_set or not.  The match length (possibly longer than
+ * 'resultlen') is returned.
+ */
+static
+int
+add_pattern(StringInfo old, int old_info, StringSet *new_str_set, char c,
+			char *pattern, char tail_pattern_initial, int resultlen)
+{
+	StringInfo	new;
+	int			info;
+	int			len;
+
+	/*
+	 * New char in the input row satisfies the condition above.
+	 */
+	new = makeStringInfo();		/* copy source string */
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+
+	/* add pattern variable char */
+	appendStringInfoChar(new, c);
+
+	/*
+	 * Adhoc optimization. If the first letter in the input string is in the
+	 * head and second position and there's no associated quatifier '+', then
+	 * we can dicard the input because there's no chance to expand the string
+	 * further.
+	 *
+	 * For example, pattern "abc" cannot match "aa".
+	 */
+	if (pattern[1] == new->data[0] &&
+		pattern[1] == new->data[1] &&
+		pattern[2] != '+' &&
+		pattern[1] != pattern[2])
+	{
+		destroyStringInfo(new);
+		return resultlen;
+	}
+
+	info = old_info;
+
+	/*
+	 * Check if we can apply "tail pattern initial optimization".  If the last
+	 * regexp component in pattern has '+' quantifier, the component is set to
+	 * the last pattern initial.  For example if pattern is "ab+",
+	 * tail_pattern_initial will become 'b'. Otherwise, tail_pattern_initial
+	 * is '\0'. If the tail pattern initial optimization is possible, we do
+	 * not need to apply regular expression match again.  Suppose we have the
+	 * previous string ended with "b" and the it was confirmed the regular
+	 * expression match, then char 'b' can be added to the string without
+	 * applying the regular expression match again.
+	 */
+	if (c == tail_pattern_initial)	/* tail pattern initial optimization
+									 * possible? */
+	{
+		/*
+		 * Is already confirmed to be matched with pattern?
+		 */
+		if ((info & STRSET_MATCHED) == 0)
+		{
+			/* not confirmed yet */
+			len = do_pattern_match(pattern, new->data, new->len);
+			if (len > 0)
+				info = STRSET_MATCHED;	/* set already confirmed flag */
+		}
+		else
+
+			/*
+			 * already confirmed. Use the string length as the matching length
+			 */
+			len = new->len;
+
+		/* update the longest match length if needed */
+		if (len > resultlen)
+			resultlen = len;
+	}
+
+	/* add new StringInfo to the string set */
+	string_set_add(new_str_set, new, info);
+
+	return resultlen;
+}
+
+/*
+ * freeze_pattern
+ *
+ * "Freeze" 'old' (along with 'old_info' flag) and add it to
+ * 'new_str_set'. Frozen string is known to not be expanded further. The frozen
+ * string is check if it satisfies 'pattern'.  If it does not, "discarded"
+ * mark is added. The discarded mark is also added if the match length is
+ * shorter than the current longest match length. The match length (possibly
+ * longer than 'resultlen') is returned.
+ */
+static
+int
+freeze_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+			   char *pattern, int resultlen)
+{
+	int			len;
+	StringInfo	new;
+	int			new_str_size;
+	int			new_index;
+
+	/*
+	 * We are freezing this pattern string.  If the pattern string length is
+	 * shorter than the current longest string length, we don't need to keep
+	 * it.
+	 */
+	if (old->len < resultlen)
+		return resultlen;
+
+	if (old_info & STRSET_MATCHED)
+		/* we don't need to apply pattern match again */
+		len = old->len;
+	else
+	{
+		/* apply pattern match */
+		len = do_pattern_match(pattern, old->data, old->len);
+		if (len <= 0)
+		{
+			/* no match. we can discard it */
+			return resultlen;
+		}
+	}
+	if (len < resultlen)
+	{
+		/* shorter match. we can discard it */
+		return resultlen;
+	}
+
+	/*
+	 * Match length is the longest so far
+	 */
+	resultlen = len;			/* remember the longest match */
+
+	/* freeze the pattern string */
+	new = makeStringInfo();
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+	/* set frozen mark */
+	string_set_add(new_str_set, new, STRSET_FROZEN);
+
+	/*
+	 * Search new_str_set to find out frozen entries that have shorter match
+	 * length. Mark them as "discard" so that they are discarded in the next
+	 * round.
+	 */
+	new_str_size =
+		string_set_get_size(new_str_set) - 1;
+
+	/* loop over new_str_set */
+	for (new_index = 0; new_index < new_str_size; new_index++)
+	{
+		int			info;
+
+		new = string_set_get(new_str_set, new_index, &info);
+
+		/*
+		 * If this is frozen and is not longer than the current longest match
+		 * length, we don't need to keep this.
+		 */
+		if (info & STRSET_FROZEN && new->len < resultlen)
+		{
+			/*
+			 * mark this set to discard in the next round
+			 */
+			info |= STRSET_DISCARDED;
+			new_str_set->info[new_index] = info;
+		}
+	}
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ *
+ * Perform pattern match using 'pattern' against 'encoded_str' whose length is
+ * 'len' bytes (without null terminate).  Returns matching number of rows if
+ * matching is succeeded.  Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str, int len)
+{
+	static regex_t *regcache = NULL;
+	static regex_t preg;
+	static char patbuf[1024];	/* most recent 'pattern' is cached here */
+	int			plen;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+	int			sts;
+	pg_wchar   *data;
+	int			data_len;
+
+	/*
+	 * Compile regexp if cache does not exist or existing cache is not same as
+	 * "pattern".
+	 */
+	if (strcmp(patbuf, pattern))
+	{
+		/*
+		 * The compiled re must live in top memory context because patbuf is
+		 * static data.
+		 */
+		MemoryContext oldContext = MemoryContextSwitchTo(TopMemoryContext);
+
+		if (regcache != NULL)
+			pg_regfree(regcache);	/* free previous re */
+
+		/* we need to convert to char to pg_wchar */
+		plen = strlen(pattern);
+		data = (pg_wchar *) palloc((plen + 1) * sizeof(pg_wchar));
+		data_len = pg_mb2wchar_with_len(pattern, data, plen);
+		/* compile re */
+		sts = pg_regcomp(&preg, /* compiled re */
+						 data,	/* target pattern */
+						 data_len,	/* length of pattern */
+						 cflags,	/* compile option */
+						 C_COLLATION_OID	/* collation */
+			);
+		pfree(data);
+
+		MemoryContextSwitchTo(oldContext);
+
+		if (sts != REG_OKAY)
+		{
+			/* re didn't compile (no need for pg_regfree, if so) */
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("invalid regular expression: %s", pattern)));
+		}
+
+		/* save cache */
+		regcache = &preg;
+		strncpy(patbuf, pattern, sizeof(patbuf));
+	}
+
+	data = (pg_wchar *) palloc((len + 1) * sizeof(pg_wchar));
+	data_len = pg_mb2wchar_with_len(encoded_str, data, len);
+
+	/* execute the regular expression match */
+	sts = pg_regexec(
+					 &preg,		/* compiled re */
+					 data,		/* target string */
+					 data_len,	/* length of encoded_str */
+					 0,			/* search start */
+					 NULL,		/* rm details */
+					 nmatch,	/* number of match sub re */
+					 pmatch,	/* match result details */
+					 eflags);
+
+	pfree(data);
+
+	if (sts != REG_OKAY)
+	{
+		if (sts != REG_NOMATCH)
+		{
+			char		errMsg[100];
+
+			pg_regerror(sts, &preg, errMsg, sizeof(errMsg));
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("regular expression failed: %s", errMsg)));
+		}
+		return 0;				/* does not match */
+	}
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+
+}
+
+/*
+ * evaluate_pattern
+ *
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ *
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ *
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ *
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *
+string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->info = palloc0(set_size * sizeof(int));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ *
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet *string_set, StringInfo str, int info)
+{
+	Size		set_size;
+	Size		old_set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		old_set_size = set_size;
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->info = repalloc0(string_set->info,
+									 old_set_size * sizeof(int),
+									 set_size * sizeof(int));
+		string_set->set_size = set_size;
+	}
+
+	string_set->info[string_set->set_index] = info;
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ *
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet *string_set, int index, int *info)
+{
+	*info = 0;
+
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	*info = string_set->info[index];
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ *
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet *string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+			destroyStringInfo(str);
+	}
+	pfree(string_set->info);
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ *
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *
+variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ *
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ *
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ *
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_build
+ *
+ * Build VariablePos structure and return it.
+ */
+static
+VariablePos *
+variable_pos_build(WindowAggState *winstate)
+{
+	VariablePos *variable_pos;
+	StringInfo	pattern_str;
+	int			initial_index = 0;
+	ListCell   *lc1,
+			   *lc2;
+
+	variable_pos = winstate->variable_pos = variable_pos_init();
+	pattern_str = winstate->pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	return variable_pos;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..3142a8bc06 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2dcc2d42da..ee11ee2c8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10664,6 +10664,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1590b64392..f96782fd6a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2585,6 +2585,37 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
+/*
+ * Allowed PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postions in PATTERN */
+} VariablePos;
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2644,6 +2675,21 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+	VariablePos *variable_pos;	/* list of pattern variable positions */
+	StringInfo	pattern_str;	/* PATTERN initials */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2671,6 +2717,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Tue_Dec_31_08_57_07_2024_963)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v27-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v27 5/9] Row pattern recognition patch (executor).
@ 2024-12-30 23:53 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-30 23:53 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1845 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   58 +
 4 files changed, 1935 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..2a06676572 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -45,9 +46,11 @@
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
+#include "regex/regex.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +162,33 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+#define	STRSET_FROZEN		(1 << 0)	/* string is frozen */
+#define	STRSET_DISCARDED	(1 << 1)	/* string is scheduled to be discarded */
+#define	STRSET_MATCHED		(1 << 2)	/* string is confirmed to be matched
+										 * with pattern */
+
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+	int		   *info;			/* an array of information bit per StringInfo.
+								 * see above */
+} StringSet;
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+} NavigationInfo;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +214,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +226,60 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(WindowAggState *winstate,
+						   StringSet *input_str_set);
+static StringSet *generate_patterns(StringSet *input_str_set,
+									char *pattern, VariablePos *variable_pos,
+									char tail_pattern_initial);
+static int	add_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+						char c, char *pattern, char tail_pattern_initial,
+						int resultlen);
+static int	freeze_pattern(StringInfo old, int old_info,
+						   StringSet *new_str_set,
+						   char *pattern, int resultlen);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str, int len);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str, int info);
+static StringInfo string_set_get(StringSet *string_set, int index, int *flag);
+static int	string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos *variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos *variable_pos, char initial,
+							   int index);
+static VariablePos *variable_pos_build(WindowAggState *winstate);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +856,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +872,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +947,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1017,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1038,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1059,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos)
+				!= RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto)
+				== RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1134,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1358,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2330,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2443,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2620,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2721,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2912,49 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
+	/*
+	 * Build variable_pos
+	 */
+	if (winstate->defineInitial)
+		winstate->variable_pos = variable_pos_build(winstate);
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2963,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3125,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3487,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3808,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3922,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4007,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4033,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4070,1430 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringSet  *str_set;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			destroyStringInfo(encoded_str);
+
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		int			init_size;
+
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+
+		/*
+		 * makeStringInfo creates initial data size to be 1024 bytes, which is
+		 * too large for us because we only need the initial data as the
+		 * number PATTERN variables (+null terminate), which is usually less
+		 * than 10 bytes. So we reallocate the initial data size as small as
+		 * the number of PATTERN variables.
+		 */
+		encoded_str = makeStringInfo();
+		pfree(encoded_str->data);
+		init_size = list_length(winstate->patternVariableList) + 1;
+		encoded_str->data = (char *) palloc0(init_size);
+		encoded_str->maxlen = init_size;
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str, 0);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		destroyStringInfo(encoded_str);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(winstate, str_set);
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ *
+ * Perform pattern matching using "pattern" against input_str_set. pattern is
+ * a regular expression string derived from PATTERN clause. Note that the
+ * regular expression string is prefixed by '^' and followed by initials
+ * represented in a same way as str_set. str_set is a set of StringInfo. Each
+ * StringInfo has a string comprising initials of pattern variable strings
+ * being true in a row. The initials are one of [a-z], parallel to the order
+ * of variable names in DEFINE clause. Suppose DEFINE has variables START, UP
+ * and DOWN. If PATTERN has START, UP+ and DOWN, then the initials in PATTERN
+ * will be 'a', 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(WindowAggState *winstate, StringSet *input_str_set)
+{
+	char	   *pattern;		/* search regexp pattern */
+	VariablePos *variable_pos;
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *new_str_set;
+	int			new_str_size;
+	int			len;
+	int			info;
+	char		tail_pattern_initial;
+
+	/*
+	 * Set last initial char to tail_pattern_initial if we can apply "tail
+	 * pattern initial optimization".  If the last regexp component in pattern
+	 * is with '+' quatifier, set the initial to tail_pattern_initial.  For
+	 * example if pattern = "ab+", tail_pattern_initial will be 'b'.
+	 * Otherwise, tail_pattern_initial is '\0'.
+	 */
+	pattern = winstate->pattern_str->data;
+	if (pattern[strlen(pattern) - 1] == '+')
+		tail_pattern_initial = pattern[strlen(pattern) - 2];
+	else
+		tail_pattern_initial = '\0';
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	variable_pos = winstate->variable_pos;
+	new_str_set = generate_patterns(input_str_set, pattern, variable_pos,
+									tail_pattern_initial);
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	len = 0;
+	resultlen = 0;
+	set_size = string_set_get_size(input_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index, &info);
+		if (s == NULL)
+			continue;			/* no data */
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		if (info & STRSET_DISCARDED)
+			continue;
+
+		len = do_pattern_match(pattern, s->data, s->len);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * generate_patterns
+ *
+ * Generate all possible pattern variable name initials in 'input_str_set' as
+ * a set of StringInfo and return it.  For example, if we have two rows having
+ * "ab" (row 0) and "ac" (row 1) in 'input str_set', returned StringSet will
+ * have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+ * 'variable_pos' and 'tail_pattern_initial' are used for pruning
+ * optimization.
+ */
+static
+StringSet *
+generate_patterns(StringSet *input_str_set, char *pattern,
+				  VariablePos *variable_pos, char tail_pattern_initial)
+{
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			index;
+	int			set_size;
+	int			old_set_size;
+	int			info;
+	int			resultlen;
+	StringInfo	str;
+	int			i;
+	char	   *p;
+
+	new_str_set = string_set_init();
+	set_size = string_set_get_size(input_str_set);
+	if (set_size == 0)			/* if there's no row in input, return empty
+								 * set */
+		return new_str_set;
+
+	resultlen = 0;
+
+	/*
+	 * Generate initial new_string_set for input row 0.
+	 */
+	str = string_set_get(input_str_set, 0, &info);
+	p = str->data;
+
+	/*
+	 * Loop over each new pattern variable char.
+	 */
+	while (*p)
+	{
+		StringInfo	new = makeStringInfo();
+
+		/* add pattern variable char */
+		appendStringInfoChar(new, *p);
+		/* add new one to string set */
+		string_set_add(new_str_set, new, 0);
+		p++;					/* next pattern variable */
+	}
+
+	/*
+	 * Generate new_string_set for each input row.
+	 */
+	for (index = 1; index < set_size; index++)
+	{
+		/* previous new str set now becomes old str set */
+		old_str_set = new_str_set;
+		new_str_set = string_set_init();	/* create new string set */
+		/* pick up input string */
+		str = string_set_get(input_str_set, index, &info);
+		old_set_size = string_set_get_size(old_str_set);
+
+		/*
+		 * Loop over each row in the previous result set.
+		 */
+		for (i = 0; i < old_set_size; i++)
+		{
+			char		last_old_char;
+			int			old_str_len;
+			int			old_info;
+			StringInfo	old;
+
+			old = string_set_get(old_str_set, i, &old_info);
+			p = old->data;
+			old_str_len = old->len;
+			if (old_str_len > 0)
+				last_old_char = p[old_str_len - 1];
+			else
+				last_old_char = '\0';
+
+			/* Can this old set be discarded? */
+			if (old_info & STRSET_DISCARDED)
+				continue;		/* discard the old string */
+
+			/* Is this old set frozen? */
+			else if (old_info & STRSET_FROZEN)
+			{
+				/* if shorter match. we can discard it */
+				if (old_str_len < resultlen)
+					continue;	/* discard the shorter string */
+
+				/* move the old set to new_str_set */
+				string_set_add(new_str_set, old, old_info);
+				old_str_set->str_set[i] = NULL;
+				continue;
+			}
+
+			/*
+			 * loop over each pattern variable initial char in the input set.
+			 */
+			for (p = str->data; *p; p++)
+			{
+				/*
+				 * Optimization.  Check if the row's pattern variable initial
+				 * character position is greater than or equal to the old
+				 * set's last pattern variable initial character position. For
+				 * example, if the old set's last pattern variable initials
+				 * are "ab", then the new pattern variable initial can be "b"
+				 * or "c" but can not be "a", if the initials in PATTERN is
+				 * something like "a b c" or "a b+ c+" etc.  This optimization
+				 * is possible when we only allow "+" quantifier.
+				 */
+				if (variable_pos_compare(variable_pos, last_old_char, *p))
+
+					/*
+					 * Satisfied the condition. Add new pattern char to
+					 * new_str_set if it looks good.
+					 */
+					resultlen = add_pattern(old, old_info, new_str_set, *p,
+											pattern, tail_pattern_initial, resultlen);
+				else
+
+					/*
+					 * The old_str did not satisfy the condition and it cannot
+					 * be extended further. "Freeze" it.
+					 */
+					resultlen = freeze_pattern(old, old_info,
+											   new_str_set, pattern, resultlen);
+			}
+		}
+		/* we no longer need old string set */
+		string_set_discard(old_str_set);
+	}
+	return new_str_set;
+}
+
+/*
+ * add_pattern
+ *
+ * Make a copy of 'old' (along with 'old_info' flag) and add new pattern char
+ * 'c' to it. Then add it to 'new_str_set'. 'pattern' and
+ * 'tail_pattern_initial' is checked to determine whether the copy is worth to
+ * add to new_str_set or not.  The match length (possibly longer than
+ * 'resultlen') is returned.
+ */
+static
+int
+add_pattern(StringInfo old, int old_info, StringSet *new_str_set, char c,
+			char *pattern, char tail_pattern_initial, int resultlen)
+{
+	StringInfo	new;
+	int			info;
+	int			len;
+
+	/*
+	 * New char in the input row satisfies the condition above.
+	 */
+	new = makeStringInfo();		/* copy source string */
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+
+	/* add pattern variable char */
+	appendStringInfoChar(new, c);
+
+	/*
+	 * Adhoc optimization. If the first letter in the input string is in the
+	 * head and second position and there's no associated quatifier '+', then
+	 * we can dicard the input because there's no chance to expand the string
+	 * further.
+	 *
+	 * For example, pattern "abc" cannot match "aa".
+	 */
+	if (pattern[1] == new->data[0] &&
+		pattern[1] == new->data[1] &&
+		pattern[2] != '+' &&
+		pattern[1] != pattern[2])
+	{
+		destroyStringInfo(new);
+		return resultlen;
+	}
+
+	info = old_info;
+
+	/*
+	 * Check if we can apply "tail pattern initial optimization".  If the last
+	 * regexp component in pattern has '+' quantifier, the component is set to
+	 * the last pattern initial.  For example if pattern is "ab+",
+	 * tail_pattern_initial will become 'b'. Otherwise, tail_pattern_initial
+	 * is '\0'. If the tail pattern initial optimization is possible, we do
+	 * not need to apply regular expression match again.  Suppose we have the
+	 * previous string ended with "b" and the it was confirmed the regular
+	 * expression match, then char 'b' can be added to the string without
+	 * applying the regular expression match again.
+	 */
+	if (c == tail_pattern_initial)	/* tail pattern initial optimization
+									 * possible? */
+	{
+		/*
+		 * Is already confirmed to be matched with pattern?
+		 */
+		if ((info & STRSET_MATCHED) == 0)
+		{
+			/* not confirmed yet */
+			len = do_pattern_match(pattern, new->data, new->len);
+			if (len > 0)
+				info = STRSET_MATCHED;	/* set already confirmed flag */
+		}
+		else
+
+			/*
+			 * already confirmed. Use the string length as the matching length
+			 */
+			len = new->len;
+
+		/* update the longest match length if needed */
+		if (len > resultlen)
+			resultlen = len;
+	}
+
+	/* add new StringInfo to the string set */
+	string_set_add(new_str_set, new, info);
+
+	return resultlen;
+}
+
+/*
+ * freeze_pattern
+ *
+ * "Freeze" 'old' (along with 'old_info' flag) and add it to
+ * 'new_str_set'. Frozen string is known to not be expanded further. The frozen
+ * string is check if it satisfies 'pattern'.  If it does not, "discarded"
+ * mark is added. The discarded mark is also added if the match length is
+ * shorter than the current longest match length. The match length (possibly
+ * longer than 'resultlen') is returned.
+ */
+static
+int
+freeze_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+			   char *pattern, int resultlen)
+{
+	int			len;
+	StringInfo	new;
+	int			new_str_size;
+	int			new_index;
+
+	/*
+	 * We are freezing this pattern string.  If the pattern string length is
+	 * shorter than the current longest string length, we don't need to keep
+	 * it.
+	 */
+	if (old->len < resultlen)
+		return resultlen;
+
+	if (old_info & STRSET_MATCHED)
+		/* we don't need to apply pattern match again */
+		len = old->len;
+	else
+	{
+		/* apply pattern match */
+		len = do_pattern_match(pattern, old->data, old->len);
+		if (len <= 0)
+		{
+			/* no match. we can discard it */
+			return resultlen;
+		}
+	}
+	if (len < resultlen)
+	{
+		/* shorter match. we can discard it */
+		return resultlen;
+	}
+
+	/*
+	 * Match length is the longest so far
+	 */
+	resultlen = len;			/* remember the longest match */
+
+	/* freeze the pattern string */
+	new = makeStringInfo();
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+	/* set frozen mark */
+	string_set_add(new_str_set, new, STRSET_FROZEN);
+
+	/*
+	 * Search new_str_set to find out frozen entries that have shorter match
+	 * length. Mark them as "discard" so that they are discarded in the next
+	 * round.
+	 */
+	new_str_size =
+		string_set_get_size(new_str_set) - 1;
+
+	/* loop over new_str_set */
+	for (new_index = 0; new_index < new_str_size; new_index++)
+	{
+		int			info;
+
+		new = string_set_get(new_str_set, new_index, &info);
+
+		/*
+		 * If this is frozen and is not longer than the current longest match
+		 * length, we don't need to keep this.
+		 */
+		if (info & STRSET_FROZEN && new->len < resultlen)
+		{
+			/*
+			 * mark this set to discard in the next round
+			 */
+			info |= STRSET_DISCARDED;
+			new_str_set->info[new_index] = info;
+		}
+	}
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ *
+ * Perform pattern match using 'pattern' against 'encoded_str' whose length is
+ * 'len' bytes (without null terminate).  Returns matching number of rows if
+ * matching is succeeded.  Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str, int len)
+{
+	static regex_t *regcache = NULL;
+	static regex_t preg;
+	static char patbuf[1024];	/* most recent 'pattern' is cached here */
+	int			plen;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+	int			sts;
+	pg_wchar   *data;
+	int			data_len;
+
+	/*
+	 * Compile regexp if cache does not exist or existing cache is not same as
+	 * "pattern".
+	 */
+	if (strcmp(patbuf, pattern))
+	{
+		/*
+		 * The compiled re must live in top memory context because patbuf is
+		 * static data.
+		 */
+		MemoryContext oldContext = MemoryContextSwitchTo(TopMemoryContext);
+
+		if (regcache != NULL)
+			pg_regfree(regcache);	/* free previous re */
+
+		/* we need to convert to char to pg_wchar */
+		plen = strlen(pattern);
+		data = (pg_wchar *) palloc((plen + 1) * sizeof(pg_wchar));
+		data_len = pg_mb2wchar_with_len(pattern, data, plen);
+		/* compile re */
+		sts = pg_regcomp(&preg, /* compiled re */
+						 data,	/* target pattern */
+						 data_len,	/* length of pattern */
+						 cflags,	/* compile option */
+						 C_COLLATION_OID	/* collation */
+			);
+		pfree(data);
+
+		MemoryContextSwitchTo(oldContext);
+
+		if (sts != REG_OKAY)
+		{
+			/* re didn't compile (no need for pg_regfree, if so) */
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("invalid regular expression: %s", pattern)));
+		}
+
+		/* save cache */
+		regcache = &preg;
+		strncpy(patbuf, pattern, sizeof(patbuf));
+	}
+
+	data = (pg_wchar *) palloc((len + 1) * sizeof(pg_wchar));
+	data_len = pg_mb2wchar_with_len(encoded_str, data, len);
+
+	/* execute the regular expression match */
+	sts = pg_regexec(
+					 &preg,		/* compiled re */
+					 data,		/* target string */
+					 data_len,	/* length of encoded_str */
+					 0,			/* search start */
+					 NULL,		/* rm details */
+					 nmatch,	/* number of match sub re */
+					 pmatch,	/* match result details */
+					 eflags);
+
+	pfree(data);
+
+	if (sts != REG_OKAY)
+	{
+		if (sts != REG_NOMATCH)
+		{
+			char		errMsg[100];
+
+			pg_regerror(sts, &preg, errMsg, sizeof(errMsg));
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("regular expression failed: %s", errMsg)));
+		}
+		return 0;				/* does not match */
+	}
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+
+}
+
+/*
+ * evaluate_pattern
+ *
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ *
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ *
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ *
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *
+string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->info = palloc0(set_size * sizeof(int));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ *
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet *string_set, StringInfo str, int info)
+{
+	Size		set_size;
+	Size		old_set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		old_set_size = set_size;
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->info = repalloc0(string_set->info,
+									 old_set_size * sizeof(int),
+									 set_size * sizeof(int));
+		string_set->set_size = set_size;
+	}
+
+	string_set->info[string_set->set_index] = info;
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ *
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet *string_set, int index, int *info)
+{
+	*info = 0;
+
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	*info = string_set->info[index];
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ *
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet *string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+			destroyStringInfo(str);
+	}
+	pfree(string_set->info);
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ *
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *
+variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ *
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ *
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ *
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_build
+ *
+ * Build VariablePos structure and return it.
+ */
+static
+VariablePos *
+variable_pos_build(WindowAggState *winstate)
+{
+	VariablePos *variable_pos;
+	StringInfo	pattern_str;
+	int			initial_index = 0;
+	ListCell   *lc1,
+			   *lc2;
+
+	variable_pos = winstate->variable_pos = variable_pos_init();
+	pattern_str = winstate->pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	return variable_pos;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..3142a8bc06 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2dcc2d42da..ee11ee2c8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10664,6 +10664,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1590b64392..f96782fd6a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2585,6 +2585,37 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
+/*
+ * Allowed PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postions in PATTERN */
+} VariablePos;
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2644,6 +2675,21 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+	VariablePos *variable_pos;	/* list of pattern variable positions */
+	StringInfo	pattern_str;	/* PATTERN initials */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2671,6 +2717,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Tue_Dec_31_08_57_07_2024_963)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v27-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH v27 5/9] Row pattern recognition patch (executor).
@ 2024-12-30 23:53 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Tatsuo Ishii @ 2024-12-30 23:53 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1845 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   58 +
 4 files changed, 1935 insertions(+), 11 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 70a7025818..2a06676572 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -45,9 +46,11 @@
 #include "optimizer/optimizer.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_coerce.h"
+#include "regex/regex.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +162,33 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+#define	STRSET_FROZEN		(1 << 0)	/* string is frozen */
+#define	STRSET_DISCARDED	(1 << 1)	/* string is scheduled to be discarded */
+#define	STRSET_MATCHED		(1 << 2)	/* string is confirmed to be matched
+										 * with pattern */
+
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+	int		   *info;			/* an array of information bit per StringInfo.
+								 * see above */
+} StringSet;
+
+/*
+ * Structure used by check_rpr_navigation() and rpr_navigation_walker().
+ */
+typedef struct NavigationInfo
+{
+	bool		is_prev;		/* true if PREV */
+	int			num_vars;		/* number of var nodes */
+} NavigationInfo;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +214,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +226,60 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(WindowAggState *winstate,
+						   StringSet *input_str_set);
+static StringSet *generate_patterns(StringSet *input_str_set,
+									char *pattern, VariablePos *variable_pos,
+									char tail_pattern_initial);
+static int	add_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+						char c, char *pattern, char tail_pattern_initial,
+						int resultlen);
+static int	freeze_pattern(StringInfo old, int old_info,
+						   StringSet *new_str_set,
+						   char *pattern, int resultlen);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str, int len);
+
+static StringSet *string_set_init(void);
+static void string_set_add(StringSet *string_set, StringInfo str, int info);
+static StringInfo string_set_get(StringSet *string_set, int index, int *flag);
+static int	string_set_get_size(StringSet *string_set);
+static void string_set_discard(StringSet *string_set);
+static VariablePos *variable_pos_init(void);
+static void variable_pos_register(VariablePos *variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos *variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos *variable_pos, char initial,
+							   int index);
+static VariablePos *variable_pos_build(WindowAggState *winstate);
+
+static void check_rpr_navigation(Node *node, bool is_prev);
+static bool rpr_navigation_walker(Node *node, void *context);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +856,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +872,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +947,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1017,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1038,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1059,53 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate, winstate->currentpos)
+				!= RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate, winstate->aggregatedupto)
+				== RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1134,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -1199,6 +1358,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2170,6 +2330,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2278,6 +2443,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2444,6 +2620,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2542,6 +2721,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2723,6 +2912,49 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
+	/*
+	 * Build variable_pos
+	 */
+	if (winstate->defineInitial)
+		winstate->variable_pos = variable_pos_build(winstate);
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2731,6 +2963,111 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var nodes that are the argument of PREV/NET so that they
+ * see scan tuple (PREV) or inner tuple (NEXT).  Also we check the arguments
+ * of PREV/NEXT include at least 1 column reference. This is required by the
+ * SQL standard.
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	bool		is_prev;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/*
+			 * The SQL standard allows to have two more arguments form of
+			 * PREV/NEXT.  But currently we allow only 1 argument form.
+			 */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			/*
+			 * Check expr of PREV/NEXT aruguments and replace varno.
+			 */
+			is_prev = (func->funcid == F_PREV) ? true : false;
+			check_rpr_navigation(node, is_prev);
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+/*
+ * Rewrite varno of Var of RPR navigation operations (PREV/NEXT).
+ * If is_prev is true, we take care PREV, otherwise NEXT.
+ */
+static void
+check_rpr_navigation(Node *node, bool is_prev)
+{
+	NavigationInfo context;
+
+	context.is_prev = is_prev;
+	context.num_vars = 0;
+	(void) expression_tree_walker(node, rpr_navigation_walker, &context);
+	if (context.num_vars < 1)
+		ereport(ERROR,
+				errmsg("row pattern navigation operation's argument must include at least one column reference"));
+}
+
+static bool
+rpr_navigation_walker(Node *node, void *context)
+{
+	NavigationInfo *nav = (NavigationInfo *) context;
+
+	if (node == NULL)
+		return false;
+
+	switch (nodeTag(node))
+	{
+		case T_Var:
+			{
+				Var		   *var = (Var *) node;
+
+				nav->num_vars++;
+
+				if (nav->is_prev)
+				{
+					/*
+					 * Rewrite varno from OUTER_VAR to regular var no so that
+					 * the var references scan tuple.
+					 */
+					var->varno = var->varnosyn;
+				}
+				else
+					var->varno = INNER_VAR;
+			}
+			break;
+		case T_Const:
+		case T_FuncExpr:
+		case T_OpExpr:
+			break;
+
+		default:
+			ereport(ERROR,
+					errmsg("row pattern navigation operation's argument includes unsupported expression"));
+	}
+	return expression_tree_walker(node, rpr_navigation_walker, context);
+}
+
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2788,6 +3125,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3148,7 +3487,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3468,14 +3808,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3542,11 +3922,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3613,6 +4007,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3631,15 +4033,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3670,3 +4070,1430 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	Assert(pos >= 0);
+
+	/*
+	 * If pos is not in the reduced frame map, it means that any info
+	 * regarding the pos has not been registered yet. So we return
+	 * RF_NOT_DETERMINED.
+	 */
+	if (pos >= winstate->alloc_sz)
+		return RF_NOT_DETERMINED;
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringSet  *str_set;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			destroyStringInfo(encoded_str);
+
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		int			init_size;
+
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+
+		/*
+		 * makeStringInfo creates initial data size to be 1024 bytes, which is
+		 * too large for us because we only need the initial data as the
+		 * number PATTERN variables (+null terminate), which is usually less
+		 * than 10 bytes. So we reallocate the initial data size as small as
+		 * the number of PATTERN variables.
+		 */
+		encoded_str = makeStringInfo();
+		pfree(encoded_str->data);
+		init_size = list_length(winstate->patternVariableList) + 1;
+		encoded_str->data = (char *) palloc0(init_size);
+		encoded_str->maxlen = init_size;
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str, 0);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		destroyStringInfo(encoded_str);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(winstate, str_set);
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ *
+ * Perform pattern matching using "pattern" against input_str_set. pattern is
+ * a regular expression string derived from PATTERN clause. Note that the
+ * regular expression string is prefixed by '^' and followed by initials
+ * represented in a same way as str_set. str_set is a set of StringInfo. Each
+ * StringInfo has a string comprising initials of pattern variable strings
+ * being true in a row. The initials are one of [a-z], parallel to the order
+ * of variable names in DEFINE clause. Suppose DEFINE has variables START, UP
+ * and DOWN. If PATTERN has START, UP+ and DOWN, then the initials in PATTERN
+ * will be 'a', 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(WindowAggState *winstate, StringSet *input_str_set)
+{
+	char	   *pattern;		/* search regexp pattern */
+	VariablePos *variable_pos;
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *new_str_set;
+	int			new_str_size;
+	int			len;
+	int			info;
+	char		tail_pattern_initial;
+
+	/*
+	 * Set last initial char to tail_pattern_initial if we can apply "tail
+	 * pattern initial optimization".  If the last regexp component in pattern
+	 * is with '+' quatifier, set the initial to tail_pattern_initial.  For
+	 * example if pattern = "ab+", tail_pattern_initial will be 'b'.
+	 * Otherwise, tail_pattern_initial is '\0'.
+	 */
+	pattern = winstate->pattern_str->data;
+	if (pattern[strlen(pattern) - 1] == '+')
+		tail_pattern_initial = pattern[strlen(pattern) - 2];
+	else
+		tail_pattern_initial = '\0';
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+	variable_pos = winstate->variable_pos;
+	new_str_set = generate_patterns(input_str_set, pattern, variable_pos,
+									tail_pattern_initial);
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+	len = 0;
+	resultlen = 0;
+	set_size = string_set_get_size(input_str_set);
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index, &info);
+		if (s == NULL)
+			continue;			/* no data */
+
+		/*
+		 * If the string is scheduled to be discarded, we just disregard it.
+		 */
+		if (info & STRSET_DISCARDED)
+			continue;
+
+		len = do_pattern_match(pattern, s->data, s->len);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * generate_patterns
+ *
+ * Generate all possible pattern variable name initials in 'input_str_set' as
+ * a set of StringInfo and return it.  For example, if we have two rows having
+ * "ab" (row 0) and "ac" (row 1) in 'input str_set', returned StringSet will
+ * have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+ * 'variable_pos' and 'tail_pattern_initial' are used for pruning
+ * optimization.
+ */
+static
+StringSet *
+generate_patterns(StringSet *input_str_set, char *pattern,
+				  VariablePos *variable_pos, char tail_pattern_initial)
+{
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			index;
+	int			set_size;
+	int			old_set_size;
+	int			info;
+	int			resultlen;
+	StringInfo	str;
+	int			i;
+	char	   *p;
+
+	new_str_set = string_set_init();
+	set_size = string_set_get_size(input_str_set);
+	if (set_size == 0)			/* if there's no row in input, return empty
+								 * set */
+		return new_str_set;
+
+	resultlen = 0;
+
+	/*
+	 * Generate initial new_string_set for input row 0.
+	 */
+	str = string_set_get(input_str_set, 0, &info);
+	p = str->data;
+
+	/*
+	 * Loop over each new pattern variable char.
+	 */
+	while (*p)
+	{
+		StringInfo	new = makeStringInfo();
+
+		/* add pattern variable char */
+		appendStringInfoChar(new, *p);
+		/* add new one to string set */
+		string_set_add(new_str_set, new, 0);
+		p++;					/* next pattern variable */
+	}
+
+	/*
+	 * Generate new_string_set for each input row.
+	 */
+	for (index = 1; index < set_size; index++)
+	{
+		/* previous new str set now becomes old str set */
+		old_str_set = new_str_set;
+		new_str_set = string_set_init();	/* create new string set */
+		/* pick up input string */
+		str = string_set_get(input_str_set, index, &info);
+		old_set_size = string_set_get_size(old_str_set);
+
+		/*
+		 * Loop over each row in the previous result set.
+		 */
+		for (i = 0; i < old_set_size; i++)
+		{
+			char		last_old_char;
+			int			old_str_len;
+			int			old_info;
+			StringInfo	old;
+
+			old = string_set_get(old_str_set, i, &old_info);
+			p = old->data;
+			old_str_len = old->len;
+			if (old_str_len > 0)
+				last_old_char = p[old_str_len - 1];
+			else
+				last_old_char = '\0';
+
+			/* Can this old set be discarded? */
+			if (old_info & STRSET_DISCARDED)
+				continue;		/* discard the old string */
+
+			/* Is this old set frozen? */
+			else if (old_info & STRSET_FROZEN)
+			{
+				/* if shorter match. we can discard it */
+				if (old_str_len < resultlen)
+					continue;	/* discard the shorter string */
+
+				/* move the old set to new_str_set */
+				string_set_add(new_str_set, old, old_info);
+				old_str_set->str_set[i] = NULL;
+				continue;
+			}
+
+			/*
+			 * loop over each pattern variable initial char in the input set.
+			 */
+			for (p = str->data; *p; p++)
+			{
+				/*
+				 * Optimization.  Check if the row's pattern variable initial
+				 * character position is greater than or equal to the old
+				 * set's last pattern variable initial character position. For
+				 * example, if the old set's last pattern variable initials
+				 * are "ab", then the new pattern variable initial can be "b"
+				 * or "c" but can not be "a", if the initials in PATTERN is
+				 * something like "a b c" or "a b+ c+" etc.  This optimization
+				 * is possible when we only allow "+" quantifier.
+				 */
+				if (variable_pos_compare(variable_pos, last_old_char, *p))
+
+					/*
+					 * Satisfied the condition. Add new pattern char to
+					 * new_str_set if it looks good.
+					 */
+					resultlen = add_pattern(old, old_info, new_str_set, *p,
+											pattern, tail_pattern_initial, resultlen);
+				else
+
+					/*
+					 * The old_str did not satisfy the condition and it cannot
+					 * be extended further. "Freeze" it.
+					 */
+					resultlen = freeze_pattern(old, old_info,
+											   new_str_set, pattern, resultlen);
+			}
+		}
+		/* we no longer need old string set */
+		string_set_discard(old_str_set);
+	}
+	return new_str_set;
+}
+
+/*
+ * add_pattern
+ *
+ * Make a copy of 'old' (along with 'old_info' flag) and add new pattern char
+ * 'c' to it. Then add it to 'new_str_set'. 'pattern' and
+ * 'tail_pattern_initial' is checked to determine whether the copy is worth to
+ * add to new_str_set or not.  The match length (possibly longer than
+ * 'resultlen') is returned.
+ */
+static
+int
+add_pattern(StringInfo old, int old_info, StringSet *new_str_set, char c,
+			char *pattern, char tail_pattern_initial, int resultlen)
+{
+	StringInfo	new;
+	int			info;
+	int			len;
+
+	/*
+	 * New char in the input row satisfies the condition above.
+	 */
+	new = makeStringInfo();		/* copy source string */
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+
+	/* add pattern variable char */
+	appendStringInfoChar(new, c);
+
+	/*
+	 * Adhoc optimization. If the first letter in the input string is in the
+	 * head and second position and there's no associated quatifier '+', then
+	 * we can dicard the input because there's no chance to expand the string
+	 * further.
+	 *
+	 * For example, pattern "abc" cannot match "aa".
+	 */
+	if (pattern[1] == new->data[0] &&
+		pattern[1] == new->data[1] &&
+		pattern[2] != '+' &&
+		pattern[1] != pattern[2])
+	{
+		destroyStringInfo(new);
+		return resultlen;
+	}
+
+	info = old_info;
+
+	/*
+	 * Check if we can apply "tail pattern initial optimization".  If the last
+	 * regexp component in pattern has '+' quantifier, the component is set to
+	 * the last pattern initial.  For example if pattern is "ab+",
+	 * tail_pattern_initial will become 'b'. Otherwise, tail_pattern_initial
+	 * is '\0'. If the tail pattern initial optimization is possible, we do
+	 * not need to apply regular expression match again.  Suppose we have the
+	 * previous string ended with "b" and the it was confirmed the regular
+	 * expression match, then char 'b' can be added to the string without
+	 * applying the regular expression match again.
+	 */
+	if (c == tail_pattern_initial)	/* tail pattern initial optimization
+									 * possible? */
+	{
+		/*
+		 * Is already confirmed to be matched with pattern?
+		 */
+		if ((info & STRSET_MATCHED) == 0)
+		{
+			/* not confirmed yet */
+			len = do_pattern_match(pattern, new->data, new->len);
+			if (len > 0)
+				info = STRSET_MATCHED;	/* set already confirmed flag */
+		}
+		else
+
+			/*
+			 * already confirmed. Use the string length as the matching length
+			 */
+			len = new->len;
+
+		/* update the longest match length if needed */
+		if (len > resultlen)
+			resultlen = len;
+	}
+
+	/* add new StringInfo to the string set */
+	string_set_add(new_str_set, new, info);
+
+	return resultlen;
+}
+
+/*
+ * freeze_pattern
+ *
+ * "Freeze" 'old' (along with 'old_info' flag) and add it to
+ * 'new_str_set'. Frozen string is known to not be expanded further. The frozen
+ * string is check if it satisfies 'pattern'.  If it does not, "discarded"
+ * mark is added. The discarded mark is also added if the match length is
+ * shorter than the current longest match length. The match length (possibly
+ * longer than 'resultlen') is returned.
+ */
+static
+int
+freeze_pattern(StringInfo old, int old_info, StringSet *new_str_set,
+			   char *pattern, int resultlen)
+{
+	int			len;
+	StringInfo	new;
+	int			new_str_size;
+	int			new_index;
+
+	/*
+	 * We are freezing this pattern string.  If the pattern string length is
+	 * shorter than the current longest string length, we don't need to keep
+	 * it.
+	 */
+	if (old->len < resultlen)
+		return resultlen;
+
+	if (old_info & STRSET_MATCHED)
+		/* we don't need to apply pattern match again */
+		len = old->len;
+	else
+	{
+		/* apply pattern match */
+		len = do_pattern_match(pattern, old->data, old->len);
+		if (len <= 0)
+		{
+			/* no match. we can discard it */
+			return resultlen;
+		}
+	}
+	if (len < resultlen)
+	{
+		/* shorter match. we can discard it */
+		return resultlen;
+	}
+
+	/*
+	 * Match length is the longest so far
+	 */
+	resultlen = len;			/* remember the longest match */
+
+	/* freeze the pattern string */
+	new = makeStringInfo();
+	enlargeStringInfo(new, old->len + 1);
+	appendStringInfoString(new, old->data);
+	/* set frozen mark */
+	string_set_add(new_str_set, new, STRSET_FROZEN);
+
+	/*
+	 * Search new_str_set to find out frozen entries that have shorter match
+	 * length. Mark them as "discard" so that they are discarded in the next
+	 * round.
+	 */
+	new_str_size =
+		string_set_get_size(new_str_set) - 1;
+
+	/* loop over new_str_set */
+	for (new_index = 0; new_index < new_str_size; new_index++)
+	{
+		int			info;
+
+		new = string_set_get(new_str_set, new_index, &info);
+
+		/*
+		 * If this is frozen and is not longer than the current longest match
+		 * length, we don't need to keep this.
+		 */
+		if (info & STRSET_FROZEN && new->len < resultlen)
+		{
+			/*
+			 * mark this set to discard in the next round
+			 */
+			info |= STRSET_DISCARDED;
+			new_str_set->info[new_index] = info;
+		}
+	}
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ *
+ * Perform pattern match using 'pattern' against 'encoded_str' whose length is
+ * 'len' bytes (without null terminate).  Returns matching number of rows if
+ * matching is succeeded.  Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str, int len)
+{
+	static regex_t *regcache = NULL;
+	static regex_t preg;
+	static char patbuf[1024];	/* most recent 'pattern' is cached here */
+	int			plen;
+	int			cflags = REG_EXTENDED;
+	size_t		nmatch = 1;
+	int			eflags = 0;
+	regmatch_t	pmatch[1];
+	int			sts;
+	pg_wchar   *data;
+	int			data_len;
+
+	/*
+	 * Compile regexp if cache does not exist or existing cache is not same as
+	 * "pattern".
+	 */
+	if (strcmp(patbuf, pattern))
+	{
+		/*
+		 * The compiled re must live in top memory context because patbuf is
+		 * static data.
+		 */
+		MemoryContext oldContext = MemoryContextSwitchTo(TopMemoryContext);
+
+		if (regcache != NULL)
+			pg_regfree(regcache);	/* free previous re */
+
+		/* we need to convert to char to pg_wchar */
+		plen = strlen(pattern);
+		data = (pg_wchar *) palloc((plen + 1) * sizeof(pg_wchar));
+		data_len = pg_mb2wchar_with_len(pattern, data, plen);
+		/* compile re */
+		sts = pg_regcomp(&preg, /* compiled re */
+						 data,	/* target pattern */
+						 data_len,	/* length of pattern */
+						 cflags,	/* compile option */
+						 C_COLLATION_OID	/* collation */
+			);
+		pfree(data);
+
+		MemoryContextSwitchTo(oldContext);
+
+		if (sts != REG_OKAY)
+		{
+			/* re didn't compile (no need for pg_regfree, if so) */
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("invalid regular expression: %s", pattern)));
+		}
+
+		/* save cache */
+		regcache = &preg;
+		strncpy(patbuf, pattern, sizeof(patbuf));
+	}
+
+	data = (pg_wchar *) palloc((len + 1) * sizeof(pg_wchar));
+	data_len = pg_mb2wchar_with_len(encoded_str, data, len);
+
+	/* execute the regular expression match */
+	sts = pg_regexec(
+					 &preg,		/* compiled re */
+					 data,		/* target string */
+					 data_len,	/* length of encoded_str */
+					 0,			/* search start */
+					 NULL,		/* rm details */
+					 nmatch,	/* number of match sub re */
+					 pmatch,	/* match result details */
+					 eflags);
+
+	pfree(data);
+
+	if (sts != REG_OKAY)
+	{
+		if (sts != REG_NOMATCH)
+		{
+			char		errMsg[100];
+
+			pg_regerror(sts, &preg, errMsg, sizeof(errMsg));
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("regular expression failed: %s", errMsg)));
+		}
+		return 0;				/* does not match */
+	}
+
+	len = pmatch[0].rm_eo;		/* return match length */
+	return len;
+
+}
+
+/*
+ * evaluate_pattern
+ *
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ *
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ *
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ *
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet *
+string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->info = palloc0(set_size * sizeof(int));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ *
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet *string_set, StringInfo str, int info)
+{
+	Size		set_size;
+	Size		old_set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		old_set_size = set_size;
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->info = repalloc0(string_set->info,
+									 old_set_size * sizeof(int),
+									 set_size * sizeof(int));
+		string_set->set_size = set_size;
+	}
+
+	string_set->info[string_set->set_index] = info;
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ *
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet *string_set, int index, int *info)
+{
+	*info = 0;
+
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	*info = string_set->info[index];
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ *
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet *string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet *string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+			destroyStringInfo(str);
+	}
+	pfree(string_set->info);
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ *
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos *
+variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ *
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos *variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ *
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos *variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ *
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos *variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_build
+ *
+ * Build VariablePos structure and return it.
+ */
+static
+VariablePos *
+variable_pos_build(WindowAggState *winstate)
+{
+	VariablePos *variable_pos;
+	StringInfo	pattern_str;
+	int			initial_index = 0;
+	ListCell   *lc1,
+			   *lc2;
+
+	variable_pos = winstate->variable_pos = variable_pos_init();
+	pattern_str = winstate->pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+	return variable_pos;
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..3142a8bc06 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+} SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2dcc2d42da..ee11ee2c8b 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10664,6 +10664,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '8126', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '8127', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1590b64392..f96782fd6a 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2585,6 +2585,37 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
+/*
+ * Allowed PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postions in PATTERN */
+} VariablePos;
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2644,6 +2675,21 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+	VariablePos *variable_pos;	/* list of pattern variable positions */
+	StringInfo	pattern_str;	/* PATTERN initials */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2671,6 +2717,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Tue_Dec_31_08_57_07_2024_963)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v27-0006-Row-pattern-recognition-patch-docs.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread

* [PATCH 2/3] replace CppAsString2(RELKIND_x) with RELKIND_x_STR
@ 2026-02-01 23:45 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 108+ messages in thread

From: Álvaro Herrera @ 2026-02-01 23:45 UTC (permalink / raw)

---
 contrib/oid2name/oid2name.c             | 18 +++---
 contrib/postgres_fdw/postgres_fdw.c     | 10 +--
 contrib/vacuumlo/vacuumlo.c             |  2 +-
 src/backend/commands/subscriptioncmds.c |  4 +-
 src/backend/utils/adt/xml.c             | 12 ++--
 src/bin/initdb/initdb.c                 | 20 +++---
 src/bin/pg_amcheck/pg_amcheck.c         | 32 ++++-----
 src/bin/pg_dump/pg_dump.c               | 36 +++++------
 src/bin/pg_upgrade/check.c              |  6 +-
 src/bin/pg_upgrade/info.c               |  6 +-
 src/bin/pg_upgrade/pg_upgrade.c         | 12 ++--
 src/bin/psql/describe.c                 | 74 ++++++++++-----------
 src/bin/psql/tab-complete.in.c          | 86 ++++++++++++-------------
 src/bin/scripts/reindexdb.c             |  8 +--
 src/bin/scripts/vacuuming.c             | 14 ++--
 15 files changed, 170 insertions(+), 170 deletions(-)

diff --git a/contrib/oid2name/oid2name.c b/contrib/oid2name/oid2name.c
index 51802907138..463bdfae320 100644
--- a/contrib/oid2name/oid2name.c
+++ b/contrib/oid2name/oid2name.c
@@ -477,8 +477,8 @@ sql_exec_dumpalltables(PGconn *conn, struct options *opts)
 			 "	LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "
 			 "	LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database(),"
 			 "	pg_catalog.pg_tablespace t "
-			 "WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ","
-			 CppAsString2(RELKIND_MATVIEW) "%s%s) AND "
+			 "WHERE relkind IN (" RELKIND_RELATION_STR ","
+			 RELKIND_MATVIEW_STR "%s%s) AND "
 			 "	%s"
 			 "		t.oid = CASE"
 			 "			WHEN reltablespace <> 0 THEN reltablespace"
@@ -486,8 +486,8 @@ sql_exec_dumpalltables(PGconn *conn, struct options *opts)
 			 "		END "
 			 "ORDER BY relname",
 			 opts->extended ? addfields : "",
-			 opts->indexes ? "," CppAsString2(RELKIND_INDEX) "," CppAsString2(RELKIND_SEQUENCE) : "",
-			 opts->systables ? "," CppAsString2(RELKIND_TOASTVALUE) : "",
+			 opts->indexes ? "," RELKIND_INDEX_STR "," RELKIND_SEQUENCE_STR : "",
+			 opts->systables ? "," RELKIND_TOASTVALUE_STR : "",
 			 opts->systables ? "" : "n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname !~ '^pg_toast' AND");
 
 	sql_exec(conn, todo, opts->quiet);
@@ -548,11 +548,11 @@ sql_exec_searchtables(PGconn *conn, struct options *opts)
 					"	LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
 					"	LEFT JOIN pg_catalog.pg_database d ON d.datname = pg_catalog.current_database(),\n"
 					"	pg_catalog.pg_tablespace t\n"
-					"WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ","
-					CppAsString2(RELKIND_MATVIEW) ","
-					CppAsString2(RELKIND_INDEX) ","
-					CppAsString2(RELKIND_SEQUENCE) ","
-					CppAsString2(RELKIND_TOASTVALUE) ") AND\n"
+					"WHERE relkind IN (" RELKIND_RELATION_STR ","
+					RELKIND_MATVIEW_STR ","
+					RELKIND_INDEX_STR ","
+					RELKIND_SEQUENCE_STR ","
+					RELKIND_TOASTVALUE_STR ") AND\n"
 					"		t.oid = CASE\n"
 					"			WHEN reltablespace <> 0 THEN reltablespace\n"
 					"			ELSE dattablespace\n"
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 3572689e33b..e0457bb1253 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -5496,11 +5496,11 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
 
 	appendStringInfoString(&buf,
 						   "WHERE c.relkind IN ("
-						   CppAsString2(RELKIND_RELATION) ","
-						   CppAsString2(RELKIND_VIEW) ","
-						   CppAsString2(RELKIND_FOREIGN_TABLE) ","
-						   CppAsString2(RELKIND_MATVIEW) ","
-						   CppAsString2(RELKIND_PARTITIONED_TABLE) ") "
+						   RELKIND_RELATION_STR ","
+						   RELKIND_VIEW_STR ","
+						   RELKIND_FOREIGN_TABLE_STR ","
+						   RELKIND_MATVIEW_STR ","
+						   RELKIND_PARTITIONED_TABLE_STR ") "
 						   "  AND n.nspname = ");
 	deparseStringLiteral(&buf, stmt->remote_schema);
 
diff --git a/contrib/vacuumlo/vacuumlo.c b/contrib/vacuumlo/vacuumlo.c
index 8102569466b..343dae9bc20 100644
--- a/contrib/vacuumlo/vacuumlo.c
+++ b/contrib/vacuumlo/vacuumlo.c
@@ -198,7 +198,7 @@ vacuumlo(const char *database, const struct _param *param)
 	strcat(buf, "      AND a.atttypid = t.oid ");
 	strcat(buf, "      AND c.relnamespace = s.oid ");
 	strcat(buf, "      AND t.typname in ('oid', 'lo') ");
-	strcat(buf, "      AND c.relkind in (" CppAsString2(RELKIND_RELATION) ", " CppAsString2(RELKIND_MATVIEW) ")");
+	strcat(buf, "      AND c.relkind in ('" RELKIND_RELATION_STR "', " RELKIND_MATVIEW_STR ")");
 	strcat(buf, "      AND s.nspname !~ '^pg_'");
 	res = PQexec(conn, buf);
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 0b3c8499b49..33e3c25a50c 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2921,7 +2921,7 @@ fetch_relation_list(WalReceiverConn *wrconn, List *publications)
 		if (server_version >= 190000)
 			appendStringInfo(&cmd,
 							 "UNION ALL\n"
-							 "  SELECT DISTINCT s.schemaname, s.sequencename, " CppAsString2(RELKIND_SEQUENCE) "::\"char\" AS relkind, NULL::int2vector AS attrs\n"
+							 "  SELECT DISTINCT s.schemaname, s.sequencename, " RELKIND_SEQUENCE_STR "::\"char\" AS relkind, NULL::int2vector AS attrs\n"
 							 "  FROM pg_catalog.pg_publication_sequences s\n"
 							 "  WHERE s.pubname IN ( %s )",
 							 pub_names.data);
@@ -2929,7 +2929,7 @@ fetch_relation_list(WalReceiverConn *wrconn, List *publications)
 	else
 	{
 		tableRow[3] = NAMEARRAYOID;
-		appendStringInfoString(&cmd, "SELECT DISTINCT t.schemaname, t.tablename, " CppAsString2(RELKIND_RELATION) "::\"char\" AS relkind \n");
+		appendStringInfoString(&cmd, "SELECT DISTINCT t.schemaname, t.tablename, " RELKIND_RELATION_STR "::\"char\" AS relkind \n");
 
 		/* Get column lists for each relation if the publisher supports it */
 		if (check_columnlist)
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index f69dc68286c..7de019a7402 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -2857,9 +2857,9 @@ schema_get_xml_visible_tables(Oid nspid)
 	initStringInfo(&query);
 	appendStringInfo(&query, "SELECT oid FROM pg_catalog.pg_class"
 					 " WHERE relnamespace = %u AND relkind IN ("
-					 CppAsString2(RELKIND_RELATION) ","
-					 CppAsString2(RELKIND_MATVIEW) ","
-					 CppAsString2(RELKIND_VIEW) ")"
+					 RELKIND_RELATION_STR ","
+					 RELKIND_MATVIEW_STR ","
+					 RELKIND_VIEW_STR ")"
 					 " AND pg_catalog.has_table_privilege (oid, 'SELECT')"
 					 " ORDER BY relname;", nspid);
 
@@ -2889,9 +2889,9 @@ database_get_xml_visible_tables(void)
 	/* At the moment there is no order required here. */
 	return query_to_oid_list("SELECT oid FROM pg_catalog.pg_class"
 							 " WHERE relkind IN ("
-							 CppAsString2(RELKIND_RELATION) ","
-							 CppAsString2(RELKIND_MATVIEW) ","
-							 CppAsString2(RELKIND_VIEW) ")"
+							 RELKIND_RELATION_STR ","
+							 RELKIND_MATVIEW_STR ","
+							 RELKIND_VIEW_STR ")"
 							 " AND pg_catalog.has_table_privilege(pg_class.oid, 'SELECT')"
 							 " AND relnamespace IN (" XML_VISIBLE_SCHEMAS ");");
 }
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index a3980e5535f..11bfdd31cbf 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -1806,12 +1806,12 @@ setup_privileges(FILE *cmdfd)
 				  "  SET relacl = (SELECT array_agg(a.acl) FROM "
 				  " (SELECT E'=r/\"%s\"' as acl "
 				  "  UNION SELECT unnest(pg_catalog.acldefault("
-				  "    CASE WHEN relkind = " CppAsString2(RELKIND_SEQUENCE) " THEN 's' "
+				  "    CASE WHEN relkind = " RELKIND_SEQUENCE_STR " THEN 's' "
 				  "         ELSE 'r' END::\"char\"," CppAsString2(BOOTSTRAP_SUPERUSERID) "::oid))"
 				  " ) as a) "
-				  "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-				  CppAsString2(RELKIND_VIEW) ", " CppAsString2(RELKIND_MATVIEW) ", "
-				  CppAsString2(RELKIND_SEQUENCE) ")"
+				  "  WHERE relkind IN (" RELKIND_RELATION_STR ", "
+				  RELKIND_VIEW_STR ", " RELKIND_MATVIEW_STR ", "
+				  RELKIND_SEQUENCE_STR ")"
 				  "  AND relacl IS NULL;\n\n",
 				  escape_quotes(username));
 	PG_CMD_PUTS("GRANT USAGE ON SCHEMA pg_catalog, public TO PUBLIC;\n\n");
@@ -1828,9 +1828,9 @@ setup_privileges(FILE *cmdfd)
 				"        pg_class"
 				"    WHERE"
 				"        relacl IS NOT NULL"
-				"        AND relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-				CppAsString2(RELKIND_VIEW) ", " CppAsString2(RELKIND_MATVIEW) ", "
-				CppAsString2(RELKIND_SEQUENCE) ");\n\n");
+				"        AND relkind IN (" RELKIND_RELATION_STR ", "
+				RELKIND_VIEW_STR ", " RELKIND_MATVIEW_STR ", "
+				RELKIND_SEQUENCE_STR ");\n\n");
 	PG_CMD_PUTS("INSERT INTO pg_init_privs "
 				"  (objoid, classoid, objsubid, initprivs, privtype)"
 				"    SELECT"
@@ -1844,9 +1844,9 @@ setup_privileges(FILE *cmdfd)
 				"        JOIN pg_attribute ON (pg_class.oid = pg_attribute.attrelid)"
 				"    WHERE"
 				"        pg_attribute.attacl IS NOT NULL"
-				"        AND pg_class.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-				CppAsString2(RELKIND_VIEW) ", " CppAsString2(RELKIND_MATVIEW) ", "
-				CppAsString2(RELKIND_SEQUENCE) ");\n\n");
+				"        AND pg_class.relkind IN (" RELKIND_RELATION_STR ", "
+				RELKIND_VIEW_STR ", " RELKIND_MATVIEW_STR ", "
+				RELKIND_SEQUENCE_STR ");\n\n");
 	PG_CMD_PUTS("INSERT INTO pg_init_privs "
 				"  (objoid, classoid, objsubid, initprivs, privtype)"
 				"    SELECT"
diff --git a/src/bin/pg_amcheck/pg_amcheck.c b/src/bin/pg_amcheck/pg_amcheck.c
index 03e24a2577c..72bfcd2c813 100644
--- a/src/bin/pg_amcheck/pg_amcheck.c
+++ b/src/bin/pg_amcheck/pg_amcheck.c
@@ -1976,28 +1976,28 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 		appendPQExpBuffer(&sql,
 						  " AND c.relam = %u "
 						  "AND c.relkind IN ("
-						  CppAsString2(RELKIND_RELATION) ", "
-						  CppAsString2(RELKIND_SEQUENCE) ", "
-						  CppAsString2(RELKIND_MATVIEW) ", "
-						  CppAsString2(RELKIND_TOASTVALUE) ") "
+						  RELKIND_RELATION_STR ", "
+						  RELKIND_SEQUENCE_STR ", "
+						  RELKIND_MATVIEW_STR ", "
+						  RELKIND_TOASTVALUE_STR ") "
 						  "AND c.relnamespace != %u",
 						  HEAP_TABLE_AM_OID, PG_TOAST_NAMESPACE);
 	else
 		appendPQExpBuffer(&sql,
 						  " AND c.relam IN (%u, %u)"
 						  "AND c.relkind IN ("
-						  CppAsString2(RELKIND_RELATION) ", "
-						  CppAsString2(RELKIND_SEQUENCE) ", "
-						  CppAsString2(RELKIND_MATVIEW) ", "
-						  CppAsString2(RELKIND_TOASTVALUE) ", "
-						  CppAsString2(RELKIND_INDEX) ") "
+						  RELKIND_RELATION_STR ", "
+						  RELKIND_SEQUENCE_STR ", "
+						  RELKIND_MATVIEW_STR ", "
+						  RELKIND_TOASTVALUE_STR ", "
+						  RELKIND_INDEX_STR ") "
 						  "AND ((c.relam = %u AND c.relkind IN ("
-						  CppAsString2(RELKIND_RELATION) ", "
-						  CppAsString2(RELKIND_SEQUENCE) ", "
-						  CppAsString2(RELKIND_MATVIEW) ", "
-						  CppAsString2(RELKIND_TOASTVALUE) ")) OR "
+						  RELKIND_RELATION_STR ", "
+						  RELKIND_SEQUENCE_STR ", "
+						  RELKIND_MATVIEW_STR ", "
+						  RELKIND_TOASTVALUE_STR ")) OR "
 						  "(c.relam = %u AND c.relkind = "
-						  CppAsString2(RELKIND_INDEX) "))",
+						  RELKIND_INDEX_STR "))",
 						  HEAP_TABLE_AM_OID, BTREE_AM_OID,
 						  HEAP_TABLE_AM_OID, BTREE_AM_OID);
 
@@ -2058,7 +2058,7 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 								 "\nWHERE true");
 		appendPQExpBuffer(&sql,
 						  " AND c.relam = %u "
-						  "AND c.relkind = " CppAsString2(RELKIND_INDEX),
+						  "AND c.relkind = " RELKIND_INDEX_STR,
 						  BTREE_AM_OID);
 		if (opts.no_toast_expansion)
 			appendPQExpBuffer(&sql,
@@ -2095,7 +2095,7 @@ compile_relation_list_one_db(PGconn *conn, SimplePtrList *relations,
 								 "\nWHERE true");
 		appendPQExpBuffer(&sql,
 						  " AND c.relam = %u"
-						  " AND c.relkind = " CppAsString2(RELKIND_INDEX) ")",
+						  " AND c.relkind = " RELKIND_INDEX_STR ")",
 						  BTREE_AM_OID);
 	}
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2bebefd0ba2..cdccccc1820 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3158,14 +3158,14 @@ buildMatViewRefreshDependencies(Archive *fout)
 						 "SELECT d1.objid, d2.refobjid, c2.relkind AS refrelkind "
 						 "FROM pg_depend d1 "
 						 "JOIN pg_class c1 ON c1.oid = d1.objid "
-						 "AND c1.relkind = " CppAsString2(RELKIND_MATVIEW)
+						 "AND c1.relkind = " RELKIND_MATVIEW_STR
 						 " JOIN pg_rewrite r1 ON r1.ev_class = d1.objid "
 						 "JOIN pg_depend d2 ON d2.classid = 'pg_rewrite'::regclass "
 						 "AND d2.objid = r1.oid "
 						 "AND d2.refobjid <> d1.objid "
 						 "JOIN pg_class c2 ON c2.oid = d2.refobjid "
-						 "AND c2.relkind IN (" CppAsString2(RELKIND_MATVIEW) ","
-						 CppAsString2(RELKIND_VIEW) ") "
+						 "AND c2.relkind IN (" RELKIND_MATVIEW_STR ","
+						 RELKIND_VIEW_STR ") "
 						 "WHERE d1.classid = 'pg_class'::regclass "
 						 "UNION "
 						 "SELECT w.objid, d3.refobjid, c3.relkind "
@@ -3175,12 +3175,12 @@ buildMatViewRefreshDependencies(Archive *fout)
 						 "AND d3.objid = r3.oid "
 						 "AND d3.refobjid <> w.refobjid "
 						 "JOIN pg_class c3 ON c3.oid = d3.refobjid "
-						 "AND c3.relkind IN (" CppAsString2(RELKIND_MATVIEW) ","
-						 CppAsString2(RELKIND_VIEW) ") "
+						 "AND c3.relkind IN (" RELKIND_MATVIEW_STR ","
+						 RELKIND_VIEW_STR ") "
 						 ") "
 						 "SELECT 'pg_class'::regclass::oid AS classid, objid, refobjid "
 						 "FROM w "
-						 "WHERE refrelkind = " CppAsString2(RELKIND_MATVIEW));
+						 "WHERE refrelkind = " RELKIND_MATVIEW_STR);
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -7279,9 +7279,9 @@ getTables(Archive *fout, int *numTables)
 						 "c.relhastriggers, c.relpersistence, "
 						 "c.reloftype, "
 						 "c.relacl, "
-						 "acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
+						 "acldefault(CASE WHEN c.relkind = " RELKIND_SEQUENCE_STR
 						 " THEN 's'::\"char\" ELSE 'r'::\"char\" END, c.relowner) AS acldefault, "
-						 "CASE WHEN c.relkind = " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN "
+						 "CASE WHEN c.relkind = " RELKIND_FOREIGN_TABLE_STR " THEN "
 						 "(SELECT ftserver FROM pg_catalog.pg_foreign_table WHERE ftrelid = c.oid) "
 						 "ELSE 0 END AS foreignserver, "
 						 "c.relfrozenxid, tc.relfrozenxid AS tfrozenxid, "
@@ -7367,7 +7367,7 @@ getTables(Archive *fout, int *numTables)
 	appendPQExpBufferStr(query,
 						 "\nFROM pg_class c\n"
 						 "LEFT JOIN pg_depend d ON "
-						 "(c.relkind = " CppAsString2(RELKIND_SEQUENCE) " AND "
+						 "(c.relkind = " RELKIND_SEQUENCE_STR " AND "
 						 "d.classid = 'pg_class'::regclass AND d.objid = c.oid AND "
 						 "d.objsubid = 0 AND "
 						 "d.refclassid = 'pg_class'::regclass AND d.deptype IN ('a', 'i'))\n"
@@ -7387,8 +7387,8 @@ getTables(Archive *fout, int *numTables)
 	 */
 	appendPQExpBufferStr(query,
 						 "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid"
-						 " AND tc.relkind = " CppAsString2(RELKIND_TOASTVALUE)
-						 " AND c.relkind <> " CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n");
+						 " AND tc.relkind = " RELKIND_TOASTVALUE_STR
+						 " AND c.relkind <> " RELKIND_PARTITIONED_TABLE_STR ")\n");
 
 	/*
 	 * Restrict to interesting relkinds (in particular, not indexes).  Not all
@@ -7402,13 +7402,13 @@ getTables(Archive *fout, int *numTables)
 	 */
 	appendPQExpBufferStr(query,
 						 "WHERE c.relkind IN ("
-						 CppAsString2(RELKIND_RELATION) ", "
-						 CppAsString2(RELKIND_SEQUENCE) ", "
-						 CppAsString2(RELKIND_VIEW) ", "
-						 CppAsString2(RELKIND_COMPOSITE_TYPE) ", "
-						 CppAsString2(RELKIND_MATVIEW) ", "
-						 CppAsString2(RELKIND_FOREIGN_TABLE) ", "
-						 CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n"
+						 RELKIND_RELATION_STR ", "
+						 RELKIND_SEQUENCE_STR ", "
+						 RELKIND_VIEW_STR ", "
+						 RELKIND_COMPOSITE_TYPE_STR ", "
+						 RELKIND_MATVIEW_STR ", "
+						 RELKIND_FOREIGN_TABLE_STR ", "
+						 RELKIND_PARTITIONED_TABLE_STR ")\n"
 						 "ORDER BY c.oid");
 
 	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index a8d20a92a98..5d053563657 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -372,9 +372,9 @@ data_type_check_query(int checknum)
 					"		NOT a.attisdropped AND "
 					"		a.atttypid IN (SELECT oid FROM oids) AND "
 					"		c.relkind IN ("
-					CppAsString2(RELKIND_RELATION) ", "
-					CppAsString2(RELKIND_MATVIEW) ", "
-					CppAsString2(RELKIND_INDEX) ") AND "
+					RELKIND_RELATION_STR ", "
+					RELKIND_MATVIEW_STR ", "
+					RELKIND_INDEX_STR ") AND "
 					"		c.relnamespace = n.oid AND "
 	/* exclude possible orphaned temp tables */
 					"		n.nspname !~ '^pg_temp_' AND "
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 47e8d1039a2..89bda44fcf9 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -508,8 +508,8 @@ get_rel_infos_query(void)
 					  "  SELECT c.oid, 0::oid, 0::oid "
 					  "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
 					  "         ON c.relnamespace = n.oid "
-					  "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-					  CppAsString2(RELKIND_MATVIEW) "%s) AND "
+					  "  WHERE relkind IN (" RELKIND_RELATION_STR ", "
+					  RELKIND_MATVIEW_STR "%s) AND "
 	/* exclude possible orphaned temp tables */
 					  "    ((n.nspname !~ '^pg_temp_' AND "
 					  "      n.nspname !~ '^pg_toast_temp_' AND "
@@ -519,7 +519,7 @@ get_rel_infos_query(void)
 					  "     (n.nspname = 'pg_catalog' AND "
 					  "      relname IN ('pg_largeobject'%s) ))), ",
 					  (user_opts.transfer_mode == TRANSFER_MODE_SWAP) ?
-					  ", " CppAsString2(RELKIND_SEQUENCE) : "",
+					  ", " RELKIND_SEQUENCE_STR : "",
 					  FirstNormalObjectId,
 					  (GET_MAJOR_VERSION(old_cluster.major_version) >= 1600) ?
 					  ", 'pg_largeobject_metadata'" : "");
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 2127d297bfe..e94f02fd369 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -990,9 +990,9 @@ set_frozenxids(bool minmxid_only)
 									  "SET	relfrozenxid = '%u' "
 			/* only heap, materialized view, and TOAST are vacuumed */
 									  "WHERE	relkind IN ("
-									  CppAsString2(RELKIND_RELATION) ", "
-									  CppAsString2(RELKIND_MATVIEW) ", "
-									  CppAsString2(RELKIND_TOASTVALUE) ")",
+									  RELKIND_RELATION_STR ", "
+									  RELKIND_MATVIEW_STR ", "
+									  RELKIND_TOASTVALUE_STR ")",
 									  old_cluster.controldata.chkpnt_nxtxid));
 
 		/* set pg_class.relminmxid */
@@ -1001,9 +1001,9 @@ set_frozenxids(bool minmxid_only)
 								  "SET	relminmxid = '%u' "
 		/* only heap, materialized view, and TOAST are vacuumed */
 								  "WHERE	relkind IN ("
-								  CppAsString2(RELKIND_RELATION) ", "
-								  CppAsString2(RELKIND_MATVIEW) ", "
-								  CppAsString2(RELKIND_TOASTVALUE) ")",
+								  RELKIND_RELATION_STR ", "
+								  RELKIND_MATVIEW_STR ", "
+								  RELKIND_TOASTVALUE_STR ")",
 								  old_cluster.controldata.chkpnt_nxtmulti));
 		PQfinish(conn);
 
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 3584c4e1428..f02a8df8875 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -689,7 +689,7 @@ describeTypes(const char *pattern, bool verbose, bool showSystem)
 	 * composite types
 	 */
 	appendPQExpBufferStr(&buf, "WHERE (t.typrelid = 0 ");
-	appendPQExpBufferStr(&buf, "OR (SELECT c.relkind = " CppAsString2(RELKIND_COMPOSITE_TYPE)
+	appendPQExpBufferStr(&buf, "OR (SELECT c.relkind = " RELKIND_COMPOSITE_TYPE_STR
 						 " FROM pg_catalog.pg_class c "
 						 "WHERE c.oid = t.typrelid))\n");
 
@@ -1063,12 +1063,12 @@ permissionsList(const char *pattern, bool showSystem)
 					  "SELECT n.nspname as \"%s\",\n"
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
-					  " WHEN " CppAsString2(RELKIND_RELATION) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_VIEW) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
+					  " WHEN " RELKIND_RELATION_STR " THEN '%s'"
+					  " WHEN " RELKIND_VIEW_STR " THEN '%s'"
+					  " WHEN " RELKIND_MATVIEW_STR " THEN '%s'"
+					  " WHEN " RELKIND_SEQUENCE_STR " THEN '%s'"
+					  " WHEN " RELKIND_FOREIGN_TABLE_STR " THEN '%s'"
+					  " WHEN " RELKIND_PARTITIONED_TABLE_STR " THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  ",
 					  gettext_noop("Schema"),
@@ -1164,12 +1164,12 @@ permissionsList(const char *pattern, bool showSystem)
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n"
 						 "     LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n"
 						 "WHERE c.relkind IN ("
-						 CppAsString2(RELKIND_RELATION) ","
-						 CppAsString2(RELKIND_VIEW) ","
-						 CppAsString2(RELKIND_MATVIEW) ","
-						 CppAsString2(RELKIND_SEQUENCE) ","
-						 CppAsString2(RELKIND_FOREIGN_TABLE) ","
-						 CppAsString2(RELKIND_PARTITIONED_TABLE) ")\n");
+						 RELKIND_RELATION_STR ","
+						 RELKIND_VIEW_STR ","
+						 RELKIND_MATVIEW_STR ","
+						 RELKIND_SEQUENCE_STR ","
+						 RELKIND_FOREIGN_TABLE_STR ","
+						 RELKIND_PARTITIONED_TABLE_STR ")\n");
 
 	if (!showSystem && !pattern)
 		appendPQExpBufferStr(&buf, "      AND n.nspname <> 'pg_catalog'\n"
@@ -3471,8 +3471,8 @@ describeOneTableDetails(const char *schemaname,
 						  "SELECT c.oid::pg_catalog.regclass\n"
 						  "FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i\n"
 						  "WHERE c.oid = i.inhparent AND i.inhrelid = '%s'\n"
-						  "  AND c.relkind != " CppAsString2(RELKIND_PARTITIONED_TABLE)
-						  " AND c.relkind != " CppAsString2(RELKIND_PARTITIONED_INDEX)
+						  "  AND c.relkind != " RELKIND_PARTITIONED_TABLE_STR
+						  " AND c.relkind != " RELKIND_PARTITIONED_INDEX_STR
 						  "\nORDER BY inhseqno;",
 						  oid);
 
@@ -4067,15 +4067,15 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 					  "SELECT n.nspname as \"%s\",\n"
 					  "  c.relname as \"%s\",\n"
 					  "  CASE c.relkind"
-					  " WHEN " CppAsString2(RELKIND_RELATION) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_VIEW) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_INDEX) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_TOASTVALUE) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
-					  " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
+					  " WHEN " RELKIND_RELATION_STR " THEN '%s'"
+					  " WHEN " RELKIND_VIEW_STR " THEN '%s'"
+					  " WHEN " RELKIND_MATVIEW_STR " THEN '%s'"
+					  " WHEN " RELKIND_INDEX_STR " THEN '%s'"
+					  " WHEN " RELKIND_SEQUENCE_STR " THEN '%s'"
+					  " WHEN " RELKIND_TOASTVALUE_STR " THEN '%s'"
+					  " WHEN " RELKIND_FOREIGN_TABLE_STR " THEN '%s'"
+					  " WHEN " RELKIND_PARTITIONED_TABLE_STR " THEN '%s'"
+					  " WHEN " RELKIND_PARTITIONED_INDEX_STR " THEN '%s'"
 					  " END as \"%s\",\n"
 					  "  pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
 					  gettext_noop("Schema"),
@@ -4157,25 +4157,25 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
 	{
-		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_RELATION) ","
-							 CppAsString2(RELKIND_PARTITIONED_TABLE) ",");
+		appendPQExpBufferStr(&buf, RELKIND_RELATION_STR ","
+							 RELKIND_PARTITIONED_TABLE_STR ",");
 		/* with 'S' or a pattern, allow 't' to match TOAST tables too */
 		if (showSystem || pattern)
-			appendPQExpBufferStr(&buf, CppAsString2(RELKIND_TOASTVALUE) ",");
+			appendPQExpBufferStr(&buf, RELKIND_TOASTVALUE_STR ",");
 	}
 	if (showViews)
-		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_VIEW) ",");
+		appendPQExpBufferStr(&buf, RELKIND_VIEW_STR ",");
 	if (showMatViews)
-		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_MATVIEW) ",");
+		appendPQExpBufferStr(&buf, RELKIND_MATVIEW_STR ",");
 	if (showIndexes)
-		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) ","
-							 CppAsString2(RELKIND_PARTITIONED_INDEX) ",");
+		appendPQExpBufferStr(&buf, RELKIND_INDEX_STR ","
+							 RELKIND_PARTITIONED_INDEX_STR ",");
 	if (showSeq)
-		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_SEQUENCE) ",");
+		appendPQExpBufferStr(&buf, RELKIND_SEQUENCE_STR ",");
 	if (showSystem || pattern)
 		appendPQExpBufferStr(&buf, "'s',"); /* was RELKIND_SPECIAL */
 	if (showForeign)
-		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_FOREIGN_TABLE) ",");
+		appendPQExpBufferStr(&buf, RELKIND_FOREIGN_TABLE_STR ",");
 
 	appendPQExpBufferStr(&buf, "''");	/* dummy */
 	appendPQExpBufferStr(&buf, ")\n");
@@ -4348,8 +4348,8 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
 	{
 		appendPQExpBuffer(&buf,
 						  ",\n  CASE c.relkind"
-						  " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
-						  " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
+						  " WHEN " RELKIND_PARTITIONED_TABLE_STR " THEN '%s'"
+						  " WHEN " RELKIND_PARTITIONED_INDEX_STR " THEN '%s'"
 						  " END as \"%s\"",
 						  gettext_noop("partitioned table"),
 						  gettext_noop("partitioned index"),
@@ -4449,9 +4449,9 @@ listPartitionedTables(const char *reltypes, const char *pattern, bool verbose)
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_PARTITIONED_TABLE) ",");
+		appendPQExpBufferStr(&buf, RELKIND_PARTITIONED_TABLE_STR ",");
 	if (showIndexes)
-		appendPQExpBufferStr(&buf, CppAsString2(RELKIND_PARTITIONED_INDEX) ",");
+		appendPQExpBufferStr(&buf, RELKIND_PARTITIONED_INDEX_STR ",");
 	appendPQExpBufferStr(&buf, "''");	/* dummy */
 	appendPQExpBufferStr(&buf, ")\n");
 
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8b91bc00062..5434a1273b2 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -146,7 +146,7 @@ typedef struct SchemaQuery
 	 * Selection condition --- only rows meeting this condition are candidates
 	 * to display.  If catname mentions multiple tables, include the necessary
 	 * join condition here.  For example, this might look like "c.relkind = "
-	 * CppAsString2(RELKIND_RELATION).  Write NULL (not an empty string) if
+	 * RELKIND_RELATION_STR.  Write NULL (not an empty string) if
 	 * not needed.
 	 */
 	const char *selcondition;
@@ -591,7 +591,7 @@ static const SchemaQuery Query_for_list_of_datatypes = {
 	.catname = "pg_catalog.pg_type t",
 	/* selcondition --- ignore table rowtypes and array types */
 	.selcondition = "(t.typrelid = 0 "
-	" OR (SELECT c.relkind = " CppAsString2(RELKIND_COMPOSITE_TYPE)
+	" OR (SELECT c.relkind = " RELKIND_COMPOSITE_TYPE_STR
 	"     FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)) "
 	"AND t.typname !~ '^_'",
 	.viscondition = "pg_catalog.pg_type_is_visible(t.oid)",
@@ -603,7 +603,7 @@ static const SchemaQuery Query_for_list_of_datatypes = {
 static const SchemaQuery Query_for_list_of_composite_datatypes = {
 	.catname = "pg_catalog.pg_type t",
 	/* selcondition --- only get composite types */
-	.selcondition = "(SELECT c.relkind = " CppAsString2(RELKIND_COMPOSITE_TYPE)
+	.selcondition = "(SELECT c.relkind = " RELKIND_COMPOSITE_TYPE_STR
 	" FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid) "
 	"AND t.typname !~ '^_'",
 	.viscondition = "pg_catalog.pg_type_is_visible(t.oid)",
@@ -679,7 +679,7 @@ static const SchemaQuery Query_for_list_of_routines = {
 
 static const SchemaQuery Query_for_list_of_sequences = {
 	.catname = "pg_catalog.pg_class c",
-	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_SEQUENCE) ")",
+	.selcondition = "c.relkind IN (" RELKIND_SEQUENCE_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -687,7 +687,7 @@ static const SchemaQuery Query_for_list_of_sequences = {
 
 static const SchemaQuery Query_for_list_of_foreign_tables = {
 	.catname = "pg_catalog.pg_class c",
-	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_FOREIGN_TABLE) ")",
+	.selcondition = "c.relkind IN (" RELKIND_FOREIGN_TABLE_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -696,8 +696,8 @@ static const SchemaQuery Query_for_list_of_foreign_tables = {
 static const SchemaQuery Query_for_list_of_tables = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
-	"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-	CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
+	"c.relkind IN (" RELKIND_RELATION_STR ", "
+	RELKIND_PARTITIONED_TABLE_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -705,7 +705,7 @@ static const SchemaQuery Query_for_list_of_tables = {
 
 static const SchemaQuery Query_for_list_of_partitioned_tables = {
 	.catname = "pg_catalog.pg_class c",
-	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
+	.selcondition = "c.relkind IN (" RELKIND_PARTITIONED_TABLE_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -714,8 +714,8 @@ static const SchemaQuery Query_for_list_of_partitioned_tables = {
 static const SchemaQuery Query_for_list_of_tables_for_constraint = {
 	.catname = "pg_catalog.pg_class c, pg_catalog.pg_constraint con",
 	.selcondition = "c.oid=con.conrelid and c.relkind IN ("
-	CppAsString2(RELKIND_RELATION) ", "
-	CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
+	RELKIND_RELATION_STR ", "
+	RELKIND_PARTITIONED_TABLE_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -783,7 +783,7 @@ static const SchemaQuery Query_for_list_of_ts_templates = {
 
 static const SchemaQuery Query_for_list_of_views = {
 	.catname = "pg_catalog.pg_class c",
-	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_VIEW) ")",
+	.selcondition = "c.relkind IN (" RELKIND_VIEW_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -791,7 +791,7 @@ static const SchemaQuery Query_for_list_of_views = {
 
 static const SchemaQuery Query_for_list_of_matviews = {
 	.catname = "pg_catalog.pg_class c",
-	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_MATVIEW) ")",
+	.selcondition = "c.relkind IN (" RELKIND_MATVIEW_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -800,8 +800,8 @@ static const SchemaQuery Query_for_list_of_matviews = {
 static const SchemaQuery Query_for_list_of_indexes = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
-	"c.relkind IN (" CppAsString2(RELKIND_INDEX) ", "
-	CppAsString2(RELKIND_PARTITIONED_INDEX) ")",
+	"c.relkind IN (" RELKIND_INDEX_STR ", "
+	RELKIND_PARTITIONED_INDEX_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -809,7 +809,7 @@ static const SchemaQuery Query_for_list_of_indexes = {
 
 static const SchemaQuery Query_for_list_of_partitioned_indexes = {
 	.catname = "pg_catalog.pg_class c",
-	.selcondition = "c.relkind = " CppAsString2(RELKIND_PARTITIONED_INDEX),
+	.selcondition = "c.relkind = " RELKIND_PARTITIONED_INDEX_STR,
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -827,8 +827,8 @@ static const SchemaQuery Query_for_list_of_relations = {
 /* partitioned relations */
 static const SchemaQuery Query_for_list_of_partitioned_relations = {
 	.catname = "pg_catalog.pg_class c",
-	.selcondition = "c.relkind IN (" CppAsString2(RELKIND_PARTITIONED_TABLE)
-	", " CppAsString2(RELKIND_PARTITIONED_INDEX) ")",
+	.selcondition = "c.relkind IN (" RELKIND_PARTITIONED_TABLE_STR
+	", " RELKIND_PARTITIONED_INDEX_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -845,10 +845,10 @@ static const SchemaQuery Query_for_list_of_operator_families = {
 static const SchemaQuery Query_for_list_of_updatables = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
-	"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-	CppAsString2(RELKIND_FOREIGN_TABLE) ", "
-	CppAsString2(RELKIND_VIEW) ", "
-	CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
+	"c.relkind IN (" RELKIND_RELATION_STR ", "
+	RELKIND_FOREIGN_TABLE_STR ", "
+	RELKIND_VIEW_STR ", "
+	RELKIND_PARTITIONED_TABLE_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -858,9 +858,9 @@ static const SchemaQuery Query_for_list_of_updatables = {
 static const SchemaQuery Query_for_list_of_mergetargets = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
-	"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-	CppAsString2(RELKIND_VIEW) ", "
-	CppAsString2(RELKIND_PARTITIONED_TABLE) ") ",
+	"c.relkind IN (" RELKIND_RELATION_STR ", "
+	RELKIND_VIEW_STR ", "
+	RELKIND_PARTITIONED_TABLE_STR ") ",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -870,12 +870,12 @@ static const SchemaQuery Query_for_list_of_mergetargets = {
 static const SchemaQuery Query_for_list_of_selectables = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
-	"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-	CppAsString2(RELKIND_SEQUENCE) ", "
-	CppAsString2(RELKIND_VIEW) ", "
-	CppAsString2(RELKIND_MATVIEW) ", "
-	CppAsString2(RELKIND_FOREIGN_TABLE) ", "
-	CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
+	"c.relkind IN (" RELKIND_RELATION_STR ", "
+	RELKIND_SEQUENCE_STR ", "
+	RELKIND_VIEW_STR ", "
+	RELKIND_MATVIEW_STR ", "
+	RELKIND_FOREIGN_TABLE_STR ", "
+	RELKIND_PARTITIONED_TABLE_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -885,9 +885,9 @@ static const SchemaQuery Query_for_list_of_selectables = {
 static const SchemaQuery Query_for_list_of_truncatables = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
-	"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-	CppAsString2(RELKIND_FOREIGN_TABLE) ", "
-	CppAsString2(RELKIND_PARTITIONED_TABLE) ")",
+	"c.relkind IN (" RELKIND_RELATION_STR ", "
+	RELKIND_FOREIGN_TABLE_STR ", "
+	RELKIND_PARTITIONED_TABLE_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -900,10 +900,10 @@ static const SchemaQuery Query_for_list_of_truncatables = {
 static const SchemaQuery Query_for_list_of_analyzables = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
-	"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-	CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
-	CppAsString2(RELKIND_MATVIEW) ", "
-	CppAsString2(RELKIND_FOREIGN_TABLE) ")",
+	"c.relkind IN (" RELKIND_RELATION_STR ", "
+	RELKIND_PARTITIONED_TABLE_STR ", "
+	RELKIND_MATVIEW_STR ", "
+	RELKIND_FOREIGN_TABLE_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -921,9 +921,9 @@ static const SchemaQuery Query_for_list_of_analyzables = {
 static const SchemaQuery Query_for_list_of_indexables = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
-	"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-	CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
-	CppAsString2(RELKIND_MATVIEW) ")",
+	"c.relkind IN (" RELKIND_RELATION_STR ", "
+	RELKIND_PARTITIONED_TABLE_STR ", "
+	RELKIND_MATVIEW_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
@@ -939,9 +939,9 @@ static const SchemaQuery Query_for_list_of_indexables = {
 static const SchemaQuery Query_for_list_of_clusterables = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
-	"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
-	CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
-	CppAsString2(RELKIND_MATVIEW) ")",
+	"c.relkind IN (" RELKIND_RELATION_STR ", "
+	RELKIND_PARTITIONED_TABLE_STR ", "
+	RELKIND_MATVIEW_STR ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
 	.result = "c.relname",
diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c
index b15745256ff..e63dda7a7a1 100644
--- a/src/bin/scripts/reindexdb.c
+++ b/src/bin/scripts/reindexdb.c
@@ -651,8 +651,8 @@ get_parallel_tables_list(PGconn *conn, ReindexType type,
 								 " ON c.relnamespace = ns.oid\n"
 								 " WHERE ns.nspname != 'pg_catalog'\n"
 								 "   AND c.relkind IN ("
-								 CppAsString2(RELKIND_RELATION) ", "
-								 CppAsString2(RELKIND_MATVIEW) ")\n"
+								 RELKIND_RELATION_STR ", "
+								 RELKIND_MATVIEW_STR ")\n"
 								 "   AND c.relpersistence != "
 								 CppAsString2(RELPERSISTENCE_TEMP) "\n"
 								 " ORDER BY c.relpages DESC;");
@@ -674,8 +674,8 @@ get_parallel_tables_list(PGconn *conn, ReindexType type,
 									 " JOIN pg_catalog.pg_namespace ns"
 									 " ON c.relnamespace = ns.oid\n"
 									 " WHERE c.relkind IN ("
-									 CppAsString2(RELKIND_RELATION) ", "
-									 CppAsString2(RELKIND_MATVIEW) ")\n"
+									 RELKIND_RELATION_STR ", "
+									 RELKIND_MATVIEW_STR ")\n"
 									 "   AND c.relpersistence != "
 									 CppAsString2(RELPERSISTENCE_TEMP) "\n"
 									 " AND ns.nspname IN (");
diff --git a/src/bin/scripts/vacuuming.c b/src/bin/scripts/vacuuming.c
index faac9089a01..9c59cd31c50 100644
--- a/src/bin/scripts/vacuuming.c
+++ b/src/bin/scripts/vacuuming.c
@@ -598,8 +598,8 @@ retrieve_objects(PGconn *conn, vacuumingOptions *vacopts,
 						 " JOIN pg_catalog.pg_namespace ns"
 						 " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
 						 " CROSS JOIN LATERAL (SELECT c.relkind IN ("
-						 CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
-						 CppAsString2(RELKIND_PARTITIONED_INDEX) ")) as p (inherited)\n"
+						 RELKIND_PARTITIONED_TABLE_STR ", "
+						 RELKIND_PARTITIONED_INDEX_STR ")) as p (inherited)\n"
 						 " LEFT JOIN pg_catalog.pg_class t"
 						 " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n");
 
@@ -659,14 +659,14 @@ retrieve_objects(PGconn *conn, vacuumingOptions *vacopts,
 		if (vacopts->mode == MODE_ANALYZE)
 			appendPQExpBufferStr(&catalog_query,
 								 " AND c.relkind OPERATOR(pg_catalog.=) ANY (array["
-								 CppAsString2(RELKIND_RELATION) ", "
-								 CppAsString2(RELKIND_MATVIEW) ", "
-								 CppAsString2(RELKIND_PARTITIONED_TABLE) "])\n");
+								 RELKIND_RELATION_STR ", "
+								 RELKIND_MATVIEW_STR ", "
+								 RELKIND_PARTITIONED_TABLE_STR "])\n");
 		else
 			appendPQExpBufferStr(&catalog_query,
 								 " AND c.relkind OPERATOR(pg_catalog.=) ANY (array["
-								 CppAsString2(RELKIND_RELATION) ", "
-								 CppAsString2(RELKIND_MATVIEW) "])\n");
+								 RELKIND_RELATION_STR ", "
+								 RELKIND_MATVIEW_STR "])\n");
 	}
 
 	/*
-- 
2.47.3


--a5gwxv7edi5gnyjn
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="0003-Initial-steps-to-making-relkind-an-enum.patch"



^ permalink  raw  reply  [nested|flat] 108+ messages in thread


end of thread, other threads:[~2026-02-01 23:45 UTC | newest]

Thread overview: 108+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2018-06-18 12:59 [PATCH 7/8] Add tests for compression methods Ildus Kurbangaliev <[email protected]>
2019-03-07 20:49 [PATCH 2/3] Add operator <->(box, point) to GiST box_ops Nikita Glukhov <[email protected]>
2019-03-07 20:49 [PATCH 2/3] Add operator <->(box, point) to GiST box_ops Nikita Glukhov <[email protected]>
2019-03-07 20:49 [PATCH 2/3] Add operator <->(box, point) to GiST box_ops Nikita Glukhov <[email protected]>
2023-06-25 11:48 [PATCH v1 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-06-25 11:48 [PATCH v1 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-06-25 11:48 [PATCH v1 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-06-25 11:48 [PATCH v1 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-06-25 11:48 [PATCH v1 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-06-26 08:05 [PATCH v2 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-06-26 08:05 [PATCH v2 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-06-26 08:05 [PATCH v2 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-06-26 08:05 [PATCH v2 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-06-26 08:05 [PATCH v2 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-07-26 10:49 [PATCH v3 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-07-26 10:49 [PATCH v3 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-07-26 10:49 [PATCH v3 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-07-26 10:49 [PATCH v3 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-08-09 07:56 [PATCH v4 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-08-09 07:56 [PATCH v4 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-08-09 07:56 [PATCH v4 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-08-09 07:56 [PATCH v4 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-08-09 07:56 [PATCH v4 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-08-09 07:56 [PATCH v4 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-02 06:32 [PATCH v5 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-02 06:32 [PATCH v5 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-02 06:32 [PATCH v5 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-02 06:32 [PATCH v5 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-02 06:32 [PATCH v5 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-02 06:32 [PATCH v5 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-12 05:22 [PATCH v6 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-12 05:22 [PATCH v6 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-12 05:22 [PATCH v6 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-12 05:22 [PATCH v6 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-12 05:22 [PATCH v6 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-12 05:22 [PATCH v6 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-12 05:22 [PATCH v6 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-22 04:53 [PATCH v7 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-22 04:53 [PATCH v7 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-22 04:53 [PATCH v7 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-22 04:53 [PATCH v7 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-25 05:01 [PATCH v8 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-25 05:01 [PATCH v8 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-25 05:01 [PATCH v8 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-09-25 05:01 [PATCH v8 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-10-04 05:51 [PATCH v9 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-10-04 05:51 [PATCH v9 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-10-04 05:51 [PATCH v9 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-10-04 05:51 [PATCH v9 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-10-22 02:22 [PATCH v10 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-10-22 02:22 [PATCH v10 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-10-22 02:22 [PATCH v10 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-10-22 02:22 [PATCH v10 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-11-08 06:57 [PATCH v11 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-11-08 06:57 [PATCH v11 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-11-08 06:57 [PATCH v11 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-12-04 11:23 [PATCH v12 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-12-04 11:23 [PATCH v12 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2023-12-04 11:23 [PATCH v12 4/7] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-01-22 09:45 [PATCH v13 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-01-22 09:45 [PATCH v13 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-01-22 09:45 [PATCH v13 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-02-28 13:59 [PATCH v14 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-02-28 13:59 [PATCH v14 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-02-28 13:59 [PATCH v14 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-03-28 10:30 [PATCH v15 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-03-28 10:30 [PATCH v15 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-03-28 10:30 [PATCH v15 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-03-28 10:30 [PATCH v15 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-03-28 10:30 [PATCH v15 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-04-04 09:08 Re: postgres_fdw fails because GMT != UTC Daniel Gustafsson <[email protected]>
2024-04-12 06:49 [PATCH v16 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-04-12 06:49 [PATCH v16 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-04-12 06:49 [PATCH v16 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-04-28 11:00 [PATCH v17 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-04-28 11:00 [PATCH v17 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-04-28 11:00 [PATCH v17 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-05-11 07:11 [PATCH v18 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-05-11 07:11 [PATCH v18 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-05-11 07:11 [PATCH v18 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-05-14 23:26 [PATCH v19 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-05-14 23:26 [PATCH v19 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-05-14 23:26 [PATCH v19 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-05-24 02:26 [PATCH v20 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-05-24 02:26 [PATCH v20 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-05-24 02:26 [PATCH v20 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-08-26 04:32 [PATCH v21 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-08-26 04:32 [PATCH v21 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-08-26 04:32 [PATCH v21 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-09-19 04:48 [PATCH v22 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-09-19 04:48 [PATCH v22 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-09-19 04:48 [PATCH v22 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-10-25 03:56 [PATCH v23 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-10-25 03:56 [PATCH v23 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-10-25 03:56 [PATCH v23 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-19 06:06 [PATCH v24 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-19 06:06 [PATCH v24 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-19 06:06 [PATCH v24 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-21 06:19 [PATCH v25 5/9] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-21 06:19 [PATCH v25 5/9] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-21 06:19 [PATCH v25 5/9] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-30 12:44 [PATCH v26 5/9] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-30 12:44 [PATCH v26 5/9] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-30 12:44 [PATCH v26 5/9] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-30 23:53 [PATCH v27 5/9] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-30 23:53 [PATCH v27 5/9] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2024-12-30 23:53 [PATCH v27 5/9] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2026-02-01 23:45 [PATCH 2/3] replace CppAsString2(RELKIND_x) with RELKIND_x_STR Álvaro Herrera <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox